Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b7732838d | ||
|
|
021b9ac5d5 | ||
|
|
5f94c7295a | ||
|
|
d9a9523c9a | ||
|
|
384d2343d5 | ||
|
|
7a387d86d6 | ||
|
|
330eef51e4 | ||
|
|
2e1a2b60c4 | ||
|
|
9896ca0e8c | ||
|
|
fd44f42f28 | ||
|
|
966ce261cb | ||
|
|
5503e7a54f | ||
|
|
b49ec93c54 | ||
|
|
e6584322fa | ||
|
|
1138cd02b0 | ||
|
|
1ba8d0cbd1 | ||
|
|
98380e2d48 |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,3 +1,39 @@
|
||||
## [0.21.0](https://github.com/stonith404/pingvin-share/compare/v0.20.3...v0.21.0) (2023-12-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **oauth:** limited discord server sign-in ([#346](https://github.com/stonith404/pingvin-share/issues/346)) ([5f94c72](https://github.com/stonith404/pingvin-share/commit/5f94c7295ab8594ed2ed615628214e869a02da2d))
|
||||
|
||||
## [0.20.3](https://github.com/stonith404/pingvin-share/compare/v0.20.2...v0.20.3) (2023-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* max expiration gets ignored if expiration is set to "never" ([330eef5](https://github.com/stonith404/pingvin-share/commit/330eef51e4f3f3fb29833bc9337e705553340aaa))
|
||||
|
||||
## [0.20.2](https://github.com/stonith404/pingvin-share/compare/v0.20.1...v0.20.2) (2023-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **oauth:** github and discord login error ([#323](https://github.com/stonith404/pingvin-share/issues/323)) ([fd44f42](https://github.com/stonith404/pingvin-share/commit/fd44f42f28c0fa2091876b138f170202d9fde04e)), closes [#322](https://github.com/stonith404/pingvin-share/issues/322) [#302](https://github.com/stonith404/pingvin-share/issues/302)
|
||||
* reverse shares couldn't be created unauthenticated ([966ce26](https://github.com/stonith404/pingvin-share/commit/966ce261cb4ad99efaadef5c36564fdfaed0d5c4))
|
||||
|
||||
## [0.20.1](https://github.com/stonith404/pingvin-share/compare/v0.20.0...v0.20.1) (2023-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* share information text color in light mode ([1138cd0](https://github.com/stonith404/pingvin-share/commit/1138cd02b0b6ac1d71c4dbc2808110c672237190))
|
||||
|
||||
## [0.20.0](https://github.com/stonith404/pingvin-share/compare/v0.19.2...v0.20.0) (2023-11-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ability to add and delete files of existing share ([#306](https://github.com/stonith404/pingvin-share/issues/306)) ([98380e2](https://github.com/stonith404/pingvin-share/commit/98380e2d48cc8ffa831d9b69cf5c0e8a40e28862))
|
||||
|
||||
## [0.19.2](https://github.com/stonith404/pingvin-share/compare/v0.19.1...v0.19.2) (2023-11-03)
|
||||
|
||||
|
||||
|
||||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^2.1.0",
|
||||
"@nestjs/common": "^10.1.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "cross-env NODE_ENV=development nest start --watch",
|
||||
|
||||
@@ -180,6 +180,10 @@ const configVariables: ConfigVariables = {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"discord-limitedGuild": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"discord-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
@@ -262,8 +266,8 @@ async function migrateConfigVariables() {
|
||||
for (const existingConfigVariable of existingConfigVariables) {
|
||||
const configVariable =
|
||||
configVariables[existingConfigVariable.category]?.[
|
||||
existingConfigVariable.name
|
||||
];
|
||||
existingConfigVariable.name
|
||||
];
|
||||
if (!configVariable) {
|
||||
await prisma.config.delete({
|
||||
where: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
@@ -81,4 +82,14 @@ export class FileController {
|
||||
|
||||
return new StreamableFile(file.file);
|
||||
}
|
||||
|
||||
@Delete(":fileId")
|
||||
@SkipThrottle()
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async remove(
|
||||
@Param("fileId") fileId: string,
|
||||
@Param("shareId") shareId: string,
|
||||
) {
|
||||
await this.fileService.remove(shareId, fileId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,18 @@ export class FileService {
|
||||
};
|
||||
}
|
||||
|
||||
async remove(shareId: string, fileId: string) {
|
||||
const fileMetaData = await this.prisma.file.findUnique({
|
||||
where: { id: fileId },
|
||||
});
|
||||
|
||||
if (!fileMetaData) throw new NotFoundException("File not found");
|
||||
|
||||
fs.unlinkSync(`${SHARE_DIRECTORY}/${shareId}/${fileId}`);
|
||||
|
||||
await this.prisma.file.delete({ where: { id: fileId } });
|
||||
}
|
||||
|
||||
async deleteAllFiles(shareId: string) {
|
||||
await fs.promises.rm(`${SHARE_DIRECTORY}/${shareId}`, {
|
||||
recursive: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ export class ErrorPageException extends Error {
|
||||
*/
|
||||
constructor(
|
||||
public readonly key: string = "default",
|
||||
public readonly redirect: string = "/",
|
||||
public readonly redirect?: string,
|
||||
public readonly params?: string[],
|
||||
) {
|
||||
super("error");
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
import { ArgumentsHost, Catch, ExceptionFilter } from "@nestjs/common";
|
||||
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { ErrorPageException } from "../exceptions/errorPage.exception";
|
||||
|
||||
@Catch(ErrorPageException)
|
||||
export class ErrorPageExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(ErrorPageExceptionFilter.name);
|
||||
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
catch(exception: ErrorPageException, host: ArgumentsHost) {
|
||||
this.logger.error(
|
||||
JSON.stringify({
|
||||
error: exception.key,
|
||||
params: exception.params,
|
||||
redirect: exception.redirect,
|
||||
}),
|
||||
);
|
||||
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
|
||||
const url = new URL(`${this.config.get("general.appUrl")}/error`);
|
||||
url.searchParams.set("redirect", exception.redirect);
|
||||
url.searchParams.set("error", exception.key);
|
||||
if (exception.redirect) {
|
||||
url.searchParams.set("redirect", exception.redirect);
|
||||
} else {
|
||||
const redirect = ctx.getRequest().cookies.access_token
|
||||
? "/account"
|
||||
: "/auth/signIn";
|
||||
url.searchParams.set("redirect", redirect);
|
||||
}
|
||||
if (exception.params) {
|
||||
url.searchParams.set("params", exception.params.join(","));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
Logger,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
|
||||
@@ -12,14 +13,20 @@ export class OAuthExceptionFilter implements ExceptionFilter {
|
||||
access_denied: "access_denied",
|
||||
expired_token: "expired_token",
|
||||
};
|
||||
private readonly logger = new Logger(OAuthExceptionFilter.name);
|
||||
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
catch(_exception: HttpException, host: ArgumentsHost) {
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
const request = ctx.getRequest();
|
||||
|
||||
this.logger.error(exception.message);
|
||||
this.logger.error(
|
||||
"Request query: " + JSON.stringify(request.query, null, 2),
|
||||
);
|
||||
|
||||
const key = this.errorKeys[request.query.error] || "default";
|
||||
|
||||
const url = new URL(`${this.config.get("general.appUrl")}/error`);
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import fetch from "node-fetch";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
import { ErrorPageException } from "../exceptions/errorPage.exception";
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
@Injectable()
|
||||
export class DiscordProvider implements OAuthProvider<DiscordToken> {
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
getAuthEndpoint(state: string): Promise<string> {
|
||||
let scope = "identify email";
|
||||
if (this.config.get("oauth.discord-limitedGuild")) {
|
||||
scope += " guilds";
|
||||
}
|
||||
return Promise.resolve(
|
||||
"https://discord.com/api/oauth2/authorize?" +
|
||||
new URLSearchParams({
|
||||
@@ -17,8 +21,8 @@ export class DiscordProvider implements OAuthProvider<DiscordToken> {
|
||||
redirect_uri:
|
||||
this.config.get("general.appUrl") + "/api/oauth/callback/discord",
|
||||
response_type: "code",
|
||||
state: state,
|
||||
scope: "identify email",
|
||||
state,
|
||||
scope,
|
||||
}).toString(),
|
||||
);
|
||||
}
|
||||
@@ -60,8 +64,8 @@ export class DiscordProvider implements OAuthProvider<DiscordToken> {
|
||||
}
|
||||
|
||||
async getUserInfo(token: OAuthToken<DiscordToken>): Promise<OAuthSignInDto> {
|
||||
const res = await fetch("https://discord.com/api/v10/user/@me", {
|
||||
method: "post",
|
||||
const res = await fetch("https://discord.com/api/v10/users/@me", {
|
||||
method: "get",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `${token.tokenType || "Bearer"} ${token.accessToken}`,
|
||||
@@ -69,7 +73,14 @@ export class DiscordProvider implements OAuthProvider<DiscordToken> {
|
||||
});
|
||||
const user = (await res.json()) as DiscordUser;
|
||||
if (user.verified === false) {
|
||||
throw new BadRequestException("Unverified account.");
|
||||
throw new ErrorPageException("unverified_account", undefined, [
|
||||
"provider_discord",
|
||||
]);
|
||||
}
|
||||
|
||||
const guild = this.config.get("oauth.discord-limitedGuild");
|
||||
if (guild) {
|
||||
await this.checkLimitedGuild(token, guild);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -79,6 +90,24 @@ export class DiscordProvider implements OAuthProvider<DiscordToken> {
|
||||
email: user.email,
|
||||
};
|
||||
}
|
||||
|
||||
async checkLimitedGuild(token: OAuthToken<DiscordToken>, guildId: string) {
|
||||
try {
|
||||
const res = await fetch("https://discord.com/api/v10/users/@me/guilds", {
|
||||
method: "get",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `${token.tokenType || "Bearer"} ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
const guilds = (await res.json()) as DiscordPartialGuild[];
|
||||
if (!guilds.some((guild) => guild.id === guildId)) {
|
||||
throw new ErrorPageException("discord_guild_permission_denied");
|
||||
}
|
||||
} catch {
|
||||
throw new ErrorPageException("discord_guild_permission_denied");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface DiscordToken {
|
||||
@@ -96,3 +125,12 @@ export interface DiscordUser {
|
||||
email: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export interface DiscordPartialGuild {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
owner: boolean;
|
||||
permissions: string;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import fetch from "node-fetch";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
@@ -7,11 +7,15 @@ import { nanoid } from "nanoid";
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
import { ErrorPageException } from "../exceptions/errorPage.exception";
|
||||
|
||||
export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
|
||||
protected discoveryUri: string;
|
||||
private configuration: OidcConfigurationCache;
|
||||
private jwk: OidcJwkCache;
|
||||
private logger: Logger = new Logger(
|
||||
Object.getPrototypeOf(this).constructor.name,
|
||||
);
|
||||
|
||||
protected constructor(
|
||||
protected name: string,
|
||||
@@ -112,7 +116,10 @@ export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
|
||||
const nonce = await this.cache.get(key);
|
||||
await this.cache.del(key);
|
||||
if (nonce !== idTokenData.nonce) {
|
||||
throw new BadRequestException("Invalid token");
|
||||
this.logger.error(
|
||||
`Invalid nonce. Expected ${nonce}, but got ${idTokenData.nonce}`,
|
||||
);
|
||||
throw new ErrorPageException("invalid_token");
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import fetch from "node-fetch";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import fetch from "node-fetch";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { ErrorPageException } from "../exceptions/errorPage.exception";
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
|
||||
@Injectable()
|
||||
export class GitHubProvider implements OAuthProvider<GitHubToken> {
|
||||
@@ -41,18 +42,19 @@ export class GitHubProvider implements OAuthProvider<GitHubToken> {
|
||||
return {
|
||||
accessToken: token.access_token,
|
||||
tokenType: token.token_type,
|
||||
scope: token.scope,
|
||||
rawToken: token,
|
||||
};
|
||||
}
|
||||
|
||||
async getUserInfo(token: OAuthToken<GitHubToken>): Promise<OAuthSignInDto> {
|
||||
const user = await this.getGitHubUser(token);
|
||||
if (!token.scope.includes("user:email")) {
|
||||
throw new BadRequestException("No email permission granted");
|
||||
throw new ErrorPageException("no_email", undefined, ["provider_github"]);
|
||||
}
|
||||
const user = await this.getGitHubUser(token);
|
||||
const email = await this.getGitHubEmail(token);
|
||||
if (!email) {
|
||||
throw new BadRequestException("No email found");
|
||||
throw new ErrorPageException("no_email", undefined, ["provider_github"]);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
@@ -7,10 +6,17 @@ import {
|
||||
import { User } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { JwtGuard } from "../../auth/guard/jwt.guard";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
|
||||
@Injectable()
|
||||
export class ShareOwnerGuard implements CanActivate {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
export class ShareOwnerGuard extends JwtGuard {
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private prisma: PrismaService,
|
||||
) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
@@ -30,6 +36,8 @@ export class ShareOwnerGuard implements CanActivate {
|
||||
|
||||
if (!share.creatorId) return true;
|
||||
|
||||
if (!(await super.canActivate(context))) return false;
|
||||
|
||||
return share.creatorId == (request.user as User).id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ export class ShareController {
|
||||
return new ShareDTO().from(await this.shareService.get(id));
|
||||
}
|
||||
|
||||
@Get(":id/from-owner")
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async getFromOwner(@Param("id") id: string) {
|
||||
return new ShareDTO().from(await this.shareService.get(id));
|
||||
}
|
||||
|
||||
@Get(":id/metaData")
|
||||
@UseGuards(ShareSecurityGuard)
|
||||
async getMetaData(@Param("id") id: string) {
|
||||
@@ -62,12 +68,6 @@ export class ShareController {
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@UseGuards(JwtGuard, ShareOwnerGuard)
|
||||
async remove(@Param("id") id: string) {
|
||||
await this.shareService.remove(id);
|
||||
}
|
||||
|
||||
@Post(":id/complete")
|
||||
@HttpCode(202)
|
||||
@UseGuards(CreateShareGuard, ShareOwnerGuard)
|
||||
@@ -78,6 +78,18 @@ export class ShareController {
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(":id/complete")
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async revertComplete(@Param("id") id: string) {
|
||||
return new ShareDTO().from(await this.shareService.revertComplete(id));
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async remove(@Param("id") id: string) {
|
||||
await this.shareService.remove(id);
|
||||
}
|
||||
|
||||
@Throttle(10, 60)
|
||||
@Get("isShareIdAvailable/:id")
|
||||
async isShareIdAvailable(@Param("id") id: string) {
|
||||
|
||||
@@ -54,10 +54,15 @@ export class ShareService {
|
||||
} else {
|
||||
const parsedExpiration = parseRelativeDateToAbsolute(share.expiration);
|
||||
|
||||
const expiresNever = moment(0).toDate() == parsedExpiration;
|
||||
|
||||
if (
|
||||
this.config.get("share.maxExpiration") !== 0 &&
|
||||
parsedExpiration >
|
||||
moment().add(this.config.get("share.maxExpiration"), "hours").toDate()
|
||||
(expiresNever ||
|
||||
parsedExpiration >
|
||||
moment()
|
||||
.add(this.config.get("share.maxExpiration"), "hours")
|
||||
.toDate())
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"Expiration date exceeds maximum expiration date",
|
||||
@@ -182,6 +187,13 @@ export class ShareService {
|
||||
});
|
||||
}
|
||||
|
||||
async revertComplete(id: string) {
|
||||
return this.prisma.share.update({
|
||||
where: { id },
|
||||
data: { uploadLocked: false, isZipReady: false },
|
||||
});
|
||||
}
|
||||
|
||||
async getSharesByUser(userId: string) {
|
||||
const shares = await this.prisma.share.findMany({
|
||||
where: {
|
||||
|
||||
@@ -10,23 +10,22 @@
|
||||
|
||||
### GitHub
|
||||
|
||||
Please follow the [official guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
|
||||
to create an OAuth app.
|
||||
Please follow the [official guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) to create an OAuth app.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/github`
|
||||
|
||||
### Google
|
||||
|
||||
Please follow the [official guide](https://developers.google.com/identity/protocols/oauth2/web-server#prerequisites) to
|
||||
create an OAuth 2.0 App.
|
||||
Please follow the [official guide](https://developers.google.com/identity/protocols/oauth2/web-server#prerequisites) to create an OAuth 2.0 App.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/google`
|
||||
|
||||
### Microsoft
|
||||
|
||||
Please follow
|
||||
the [official guide](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app)
|
||||
to register an application.
|
||||
Please follow the [official guide](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) to register an application.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Microsoft Tenant** you set in the admin panel must match the **supported account types** you set in the Microsoft Entra admin center, otherwise the OAuth login will not work. Refer to the [official documentation](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri) for more details.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/microsoft`
|
||||
|
||||
@@ -38,7 +37,7 @@ Redirect URL: `https://<your-domain>/api/oauth/callback/discord`
|
||||
|
||||
### OpenID Connect
|
||||
|
||||
Generic OpenID Connect provider is also supported, we have tested it on Keycloak and Authentik.
|
||||
Generic OpenID Connect provider is also supported, we have tested it on Keycloak, Authentik and Casdoor.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/oidc`
|
||||
|
||||
@@ -74,14 +73,11 @@ const configVariables: ConfigVariables = {
|
||||
|
||||
### 2. Create provider class
|
||||
|
||||
#### OpenID Connect
|
||||
#### Generic OpenID Connect
|
||||
|
||||
If your provider supports OpenID connect, it's extremely easy to
|
||||
extend [`GenericOidcProvider`](../backend/src/oauth/provider/genericOidc.provider.ts) to add a new OpenID Connect
|
||||
provider.
|
||||
If your provider supports OpenID connect, it's extremely easy to extend [`GenericOidcProvider`](../backend/src/oauth/provider/genericOidc.provider.ts) to add a new OpenID Connect provider.
|
||||
|
||||
The [Google provider](../backend/src/oauth/provider/google.provider.ts)
|
||||
and [Microsoft provider](../backend/src/oauth/provider/microsoft.provider.ts) are good examples.
|
||||
The [Google provider](../backend/src/oauth/provider/google.provider.ts) and [Microsoft provider](../backend/src/oauth/provider/microsoft.provider.ts) are good examples.
|
||||
|
||||
Here are some discovery URIs for popular providers:
|
||||
|
||||
@@ -95,17 +91,13 @@ Here are some discovery URIs for popular providers:
|
||||
|
||||
#### OAuth 2
|
||||
|
||||
If your provider only supports OAuth 2, you can
|
||||
implement [`OAuthProvider`](../backend/src/oauth/provider/oauthProvider.interface.ts) interface to add a new OAuth 2
|
||||
provider.
|
||||
If your provider only supports OAuth 2, you can implement [`OAuthProvider`](../backend/src/oauth/provider/oauthProvider.interface.ts) interface to add a new OAuth 2 provider.
|
||||
|
||||
The [GitHub provider](../backend/src/oauth/provider/github.provider.ts)
|
||||
and [Discord provider](../backend/src/oauth/provider/discord.provider.ts) are good examples.
|
||||
The [GitHub provider](../backend/src/oauth/provider/github.provider.ts) and [Discord provider](../backend/src/oauth/provider/discord.provider.ts) are good examples.
|
||||
|
||||
### 3. Register provider
|
||||
|
||||
Register your provider in [`OAuthModule`](../backend/src/oauth/oauth.module.ts)
|
||||
and [`OAuthSignInDto`](../backend/src/oauth/dto/oauthSignIn.dto.ts):
|
||||
Register your provider in [`OAuthModule`](../backend/src/oauth/oauth.module.ts) and [`OAuthSignInDto`](../backend/src/oauth/dto/oauthSignIn.dto.ts):
|
||||
|
||||
```ts
|
||||
@Module({
|
||||
@@ -117,8 +109,7 @@ and [`OAuthSignInDto`](../backend/src/oauth/dto/oauthSignIn.dto.ts):
|
||||
useFactory(github: GitHubProvider, /* your provider */): Record<string, OAuthProvider<unknown>> {
|
||||
return {
|
||||
github,
|
||||
google,
|
||||
oidc,
|
||||
/* your provider */
|
||||
};
|
||||
},
|
||||
inject: [GitHubProvider, /* your provider */],
|
||||
@@ -131,8 +122,7 @@ export class OAuthModule {
|
||||
|
||||
```ts
|
||||
export interface OAuthSignInDto {
|
||||
provider: 'github' | 'google' | 'microsoft' | 'discord' | 'oidc' /* your provider*/
|
||||
;
|
||||
provider: 'github' | 'google' | 'microsoft' | 'discord' | 'oidc' /* your provider*/;
|
||||
providerId: string;
|
||||
providerUsername: string;
|
||||
email: string;
|
||||
@@ -161,8 +151,7 @@ Add keys below to your i18n text in [locale file](../frontend/src/i18n/translati
|
||||
- `admin.config.oauth.YOUR_PROVIDER_NAME-enabled`
|
||||
- `admin.config.oauth.YOUR_PROVIDER_NAME-client-id`
|
||||
- `admin.config.oauth.YOUR_PROVIDER_NAME-client-secret`
|
||||
- `error.param.provider_YOUR_PROVIDER_NAME`
|
||||
- Other config keys you defined in step 1
|
||||
|
||||
Congratulations! 🎉 You have successfully added a new OAuth 2 provider! Pull requests are welcome if you want to share
|
||||
your provider with others.
|
||||
|
||||
Congratulations! 🎉 You have successfully added a new OAuth 2 provider! Pull requests are welcome if you want to share your provider with others.
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
@@ -36,28 +36,28 @@ const showShareInformationsModal = (
|
||||
|
||||
children: (
|
||||
<Stack align="stretch" spacing="md">
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.id" />:{" "}
|
||||
</b>
|
||||
{share.id}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.description" />:{" "}
|
||||
</b>
|
||||
{share.description || "No description"}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.createdAt" />:{" "}
|
||||
</b>
|
||||
{formattedCreatedAt}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.expiresAt" />:{" "}
|
||||
</b>
|
||||
@@ -66,7 +66,7 @@ const showShareInformationsModal = (
|
||||
<Divider />
|
||||
<CopyTextField link={link} />
|
||||
<Divider />
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.size" />:{" "}
|
||||
</b>
|
||||
@@ -76,7 +76,7 @@ const showShareInformationsModal = (
|
||||
|
||||
<Flex align="center" justify="center">
|
||||
{shareSize / maxShareSize < 0.1 && (
|
||||
<Text size="xs" color="lightgray" style={{ marginRight: "4px" }}>
|
||||
<Text size="xs" style={{ marginRight: "4px" }}>
|
||||
{formattedShareSize}
|
||||
</Text>
|
||||
)}
|
||||
@@ -87,7 +87,7 @@ const showShareInformationsModal = (
|
||||
size="xl"
|
||||
radius="xl"
|
||||
/>
|
||||
<Text size="xs" color="lightgray" style={{ marginLeft: "4px" }}>
|
||||
<Text size="xs" style={{ marginLeft: "4px" }}>
|
||||
{formattedMaxShareSize}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -33,10 +33,12 @@ const useStyles = createStyles((theme) => ({
|
||||
}));
|
||||
|
||||
const Dropzone = ({
|
||||
title,
|
||||
isUploading,
|
||||
maxShareSize,
|
||||
showCreateUploadModalCallback,
|
||||
}: {
|
||||
title?: string;
|
||||
isUploading: boolean;
|
||||
maxShareSize: number;
|
||||
showCreateUploadModalCallback: (files: FileUpload[]) => void;
|
||||
@@ -78,7 +80,7 @@ const Dropzone = ({
|
||||
<TbCloudUpload size={50} />
|
||||
</Group>
|
||||
<Text align="center" weight={700} size="lg" mt="xl">
|
||||
<FormattedMessage id="upload.dropzone.title" />
|
||||
{title || <FormattedMessage id="upload.dropzone.title" />}
|
||||
</Text>
|
||||
<Text align="center" size="sm" mt="xs" color="dimmed">
|
||||
<FormattedMessage
|
||||
|
||||
238
frontend/src/components/upload/EditableUpload.tsx
Normal file
238
frontend/src/components/upload/EditableUpload.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import { Button, Group } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { cleanNotifications } from "@mantine/notifications";
|
||||
import { AxiosError } from "axios";
|
||||
import pLimit from "p-limit";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Dropzone from "../../components/upload/Dropzone";
|
||||
import FileList from "../../components/upload/FileList";
|
||||
import showCompletedUploadModal from "../../components/upload/modals/showCompletedUploadModal";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
import { FileListItem, FileMetaData, FileUpload } from "../../types/File.type";
|
||||
import toast from "../../utils/toast.util";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const promiseLimit = pLimit(3);
|
||||
const chunkSize = 10 * 1024 * 1024; // 10MB
|
||||
let errorToastShown = false;
|
||||
|
||||
const EditableUpload = ({
|
||||
maxShareSize,
|
||||
shareId,
|
||||
files: savedFiles = [],
|
||||
}: {
|
||||
maxShareSize?: number;
|
||||
isReverseShare?: boolean;
|
||||
shareId: string;
|
||||
files?: FileMetaData[];
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
const router = useRouter();
|
||||
const config = useConfig();
|
||||
|
||||
const [existingFiles, setExistingFiles] =
|
||||
useState<Array<FileMetaData & { deleted?: boolean }>>(savedFiles);
|
||||
const [uploadingFiles, setUploadingFiles] = useState<FileUpload[]>([]);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const existingAndUploadedFiles: FileListItem[] = useMemo(
|
||||
() => [...uploadingFiles, ...existingFiles],
|
||||
[existingFiles, uploadingFiles],
|
||||
);
|
||||
const dirty = useMemo(() => {
|
||||
return (
|
||||
existingFiles.some((file) => !!file.deleted) || !!uploadingFiles.length
|
||||
);
|
||||
}, [existingFiles, uploadingFiles]);
|
||||
|
||||
const setFiles = (files: FileListItem[]) => {
|
||||
const _uploadFiles = files.filter(
|
||||
(file) => "uploadingProgress" in file,
|
||||
) as FileUpload[];
|
||||
const _existingFiles = files.filter(
|
||||
(file) => !("uploadingProgress" in file),
|
||||
) as FileMetaData[];
|
||||
|
||||
setUploadingFiles(_uploadFiles);
|
||||
setExistingFiles(_existingFiles);
|
||||
};
|
||||
|
||||
maxShareSize ??= parseInt(config.get("share.maxSize"));
|
||||
|
||||
const uploadFiles = async (files: FileUpload[]) => {
|
||||
const fileUploadPromises = files.map(async (file, fileIndex) =>
|
||||
// Limit the number of concurrent uploads to 3
|
||||
promiseLimit(async () => {
|
||||
let fileId: string;
|
||||
|
||||
const setFileProgress = (progress: number) => {
|
||||
setUploadingFiles((files) =>
|
||||
files.map((file, callbackIndex) => {
|
||||
if (fileIndex == callbackIndex) {
|
||||
file.uploadingProgress = progress;
|
||||
}
|
||||
return file;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
setFileProgress(1);
|
||||
|
||||
let chunks = Math.ceil(file.size / chunkSize);
|
||||
|
||||
// If the file is 0 bytes, we still need to upload 1 chunk
|
||||
if (chunks == 0) chunks++;
|
||||
|
||||
for (let chunkIndex = 0; chunkIndex < chunks; chunkIndex++) {
|
||||
const from = chunkIndex * chunkSize;
|
||||
const to = from + chunkSize;
|
||||
const blob = file.slice(from, to);
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) =>
|
||||
await shareService
|
||||
.uploadFile(
|
||||
shareId,
|
||||
event,
|
||||
{
|
||||
id: fileId,
|
||||
name: file.name,
|
||||
},
|
||||
chunkIndex,
|
||||
chunks,
|
||||
)
|
||||
.then((response) => {
|
||||
fileId = response.id;
|
||||
resolve(response);
|
||||
})
|
||||
.catch(reject);
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
setFileProgress(((chunkIndex + 1) / chunks) * 100);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof AxiosError &&
|
||||
e.response?.data.error == "unexpected_chunk_index"
|
||||
) {
|
||||
// Retry with the expected chunk index
|
||||
chunkIndex = e.response!.data!.expectedChunkIndex - 1;
|
||||
continue;
|
||||
} else {
|
||||
setFileProgress(-1);
|
||||
// Retry after 5 seconds
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
chunkIndex = -1;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(fileUploadPromises);
|
||||
};
|
||||
|
||||
const removeFiles = async () => {
|
||||
const removedFiles = existingFiles.filter((file) => !!file.deleted);
|
||||
|
||||
if (removedFiles.length > 0) {
|
||||
await Promise.all(
|
||||
removedFiles.map(async (file) => {
|
||||
await shareService.removeFile(shareId, file.id);
|
||||
}),
|
||||
);
|
||||
|
||||
setExistingFiles(existingFiles.filter((file) => !file.deleted));
|
||||
}
|
||||
};
|
||||
|
||||
const revertComplete = async () => {
|
||||
await shareService.revertComplete(shareId).then();
|
||||
};
|
||||
|
||||
const completeShare = async () => {
|
||||
return await shareService.completeShare(shareId);
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
setIsUploading(true);
|
||||
|
||||
try {
|
||||
await revertComplete();
|
||||
await uploadFiles(uploadingFiles);
|
||||
|
||||
const hasFailed = uploadingFiles.some(
|
||||
(file) => file.uploadingProgress == -1,
|
||||
);
|
||||
|
||||
if (!hasFailed) {
|
||||
await removeFiles();
|
||||
}
|
||||
|
||||
await completeShare();
|
||||
|
||||
if (!hasFailed) {
|
||||
toast.success(t("share.edit.notify.save-success"));
|
||||
router.back();
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("share.edit.notify.generic-error"));
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const appendFiles = (appendingFiles: FileUpload[]) => {
|
||||
setUploadingFiles([...appendingFiles, ...uploadingFiles]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check if there are any files that failed to upload
|
||||
const fileErrorCount = uploadingFiles.filter(
|
||||
(file) => file.uploadingProgress == -1,
|
||||
).length;
|
||||
|
||||
if (fileErrorCount > 0) {
|
||||
if (!errorToastShown) {
|
||||
toast.error(
|
||||
t("upload.notify.count-failed", { count: fileErrorCount }),
|
||||
{
|
||||
withCloseButton: false,
|
||||
autoClose: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
errorToastShown = true;
|
||||
} else {
|
||||
cleanNotifications();
|
||||
errorToastShown = false;
|
||||
}
|
||||
}, [uploadingFiles]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group position="right" mb={20}>
|
||||
<Button loading={isUploading} disabled={!dirty} onClick={() => save()}>
|
||||
<FormattedMessage id="common.button.save" />
|
||||
</Button>
|
||||
</Group>
|
||||
<Dropzone
|
||||
title={t("share.edit.append-upload")}
|
||||
maxShareSize={maxShareSize}
|
||||
showCreateUploadModalCallback={appendFiles}
|
||||
isUploading={isUploading}
|
||||
/>
|
||||
{existingAndUploadedFiles.length > 0 && (
|
||||
<FileList files={existingAndUploadedFiles} setFiles={setFiles} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default EditableUpload;
|
||||
@@ -1,41 +1,106 @@
|
||||
import { ActionIcon, Table } from "@mantine/core";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { TbTrash } from "react-icons/tb";
|
||||
import { FileUpload } from "../../types/File.type";
|
||||
import { GrUndo } from "react-icons/gr";
|
||||
import { FileListItem } from "../../types/File.type";
|
||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||
import UploadProgressIndicator from "./UploadProgressIndicator";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const FileList = ({
|
||||
const FileListRow = ({
|
||||
file,
|
||||
onRemove,
|
||||
onRestore,
|
||||
}: {
|
||||
file: FileListItem;
|
||||
onRemove?: () => void;
|
||||
onRestore?: () => void;
|
||||
}) => {
|
||||
{
|
||||
const uploadable = "uploadingProgress" in file;
|
||||
const uploading = uploadable && file.uploadingProgress !== 0;
|
||||
const removable = uploadable
|
||||
? file.uploadingProgress === 0
|
||||
: onRemove && !file.deleted;
|
||||
const restorable = onRestore && !uploadable && !!file.deleted; // maybe undefined, force boolean
|
||||
const deleted = !uploadable && !!file.deleted;
|
||||
|
||||
return (
|
||||
<tr
|
||||
style={{
|
||||
color: deleted ? "rgba(120, 120, 120, 0.5)" : "inherit",
|
||||
textDecoration: deleted ? "line-through" : "none",
|
||||
}}
|
||||
>
|
||||
<td>{file.name}</td>
|
||||
<td>{byteToHumanSizeString(+file.size)}</td>
|
||||
<td>
|
||||
{removable && (
|
||||
<ActionIcon
|
||||
color="red"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={onRemove}
|
||||
>
|
||||
<TbTrash />
|
||||
</ActionIcon>
|
||||
)}
|
||||
{uploading && (
|
||||
<UploadProgressIndicator progress={file.uploadingProgress} />
|
||||
)}
|
||||
{restorable && (
|
||||
<ActionIcon
|
||||
color="primary"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={onRestore}
|
||||
>
|
||||
<GrUndo />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const FileList = <T extends FileListItem = FileListItem>({
|
||||
files,
|
||||
setFiles,
|
||||
}: {
|
||||
files: FileUpload[];
|
||||
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
|
||||
files: T[];
|
||||
setFiles: (files: T[]) => void;
|
||||
}) => {
|
||||
const remove = (index: number) => {
|
||||
files.splice(index, 1);
|
||||
const file = files[index];
|
||||
|
||||
if ("uploadingProgress" in file) {
|
||||
files.splice(index, 1);
|
||||
} else {
|
||||
files[index] = { ...file, deleted: true };
|
||||
}
|
||||
|
||||
setFiles([...files]);
|
||||
};
|
||||
|
||||
const restore = (index: number) => {
|
||||
const file = files[index];
|
||||
|
||||
if ("uploadingProgress" in file) {
|
||||
return;
|
||||
} else {
|
||||
files[index] = { ...file, deleted: false };
|
||||
}
|
||||
|
||||
setFiles([...files]);
|
||||
};
|
||||
|
||||
const rows = files.map((file, i) => (
|
||||
<tr key={i}>
|
||||
<td>{file.name}</td>
|
||||
<td>{byteToHumanSizeString(file.size)}</td>
|
||||
<td>
|
||||
{file.uploadingProgress == 0 ? (
|
||||
<ActionIcon
|
||||
color="red"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={() => remove(i)}
|
||||
>
|
||||
<TbTrash />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<UploadProgressIndicator progress={file.uploadingProgress} />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<FileListRow
|
||||
key={i}
|
||||
file={file}
|
||||
onRemove={() => remove(i)}
|
||||
onRestore={() => restore(i)}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
|
||||
@@ -125,11 +125,13 @@ const CreateUploadModalBody = ({
|
||||
"",
|
||||
) as moment.unitOfTime.DurationConstructor,
|
||||
);
|
||||
|
||||
if (
|
||||
options.maxExpirationInHours != 0 &&
|
||||
expirationDate.isAfter(
|
||||
moment().add(options.maxExpirationInHours, "hours"),
|
||||
)
|
||||
(form.values.never_expires ||
|
||||
expirationDate.isAfter(
|
||||
moment().add(options.maxExpirationInHours, "hours"),
|
||||
))
|
||||
) {
|
||||
form.setFieldError(
|
||||
"expiration_num",
|
||||
|
||||
@@ -6,12 +6,14 @@ import finnish from "./translations/fi-FI";
|
||||
import french from "./translations/fr-FR";
|
||||
import japanese from "./translations/ja-JP";
|
||||
import dutch from "./translations/nl-BE";
|
||||
import polish from "./translations/pl-PL";
|
||||
import portuguese from "./translations/pt-BR";
|
||||
import russian from "./translations/ru-RU";
|
||||
import serbian from "./translations/sr-SP";
|
||||
import swedish from "./translations/sv-SE";
|
||||
import thai from "./translations/th-TH";
|
||||
import chineseSimplified from "./translations/zh-CN";
|
||||
import polish from "./translations/pl-PL";
|
||||
import chineseTraditional from "./translations/zh-TW";
|
||||
|
||||
export const LOCALES = {
|
||||
ENGLISH: {
|
||||
@@ -49,6 +51,11 @@ export const LOCALES = {
|
||||
code: "zh-CN",
|
||||
messages: chineseSimplified,
|
||||
},
|
||||
CHINESE_TRADITIONAL: {
|
||||
name: "正體中文",
|
||||
code: "zh-TW",
|
||||
messages: chineseTraditional,
|
||||
},
|
||||
FINNISH: {
|
||||
name: "Suomi",
|
||||
code: "fi-FI",
|
||||
@@ -84,4 +91,9 @@ export const LOCALES = {
|
||||
code: "pl-PL",
|
||||
messages: polish,
|
||||
},
|
||||
SWEDISH: {
|
||||
name: "Svenska",
|
||||
code: "sv-SE",
|
||||
messages: swedish,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Forhåndsvisning ikke understøttet",
|
||||
"share.modal.file-preview.error.not-supported.description": "En forhåndsvisning for thise filtype er ikke understøttet. Download venligst filen for at se den.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Konfiguration",
|
||||
"admin.config.category.general": "Generelt",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
"account.shares.title.empty": "Es ist so leer hier 👀",
|
||||
"account.shares.description.empty": "Du hast keine Freigaben erstellt.",
|
||||
"account.shares.button.create": "Erstelle eine",
|
||||
"account.shares.info.title": "Teile deine Information",
|
||||
"account.shares.info.title": "Freigabe Informationen",
|
||||
"account.shares.table.id": "ID",
|
||||
"account.shares.table.name": "Name",
|
||||
"account.shares.table.description": "Beschreibung",
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Vorschau wird nicht unterstützt",
|
||||
"share.modal.file-preview.error.not-supported.description": "Eine Vorschau für diesen Dateityp wird nicht unterstützt. Bitte lade die Datei herunter, um sie anzuzeigen.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Einstellungen",
|
||||
"admin.config.category.general": "Allgemein",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -363,16 +371,19 @@ export default {
|
||||
"404.description": "Ups, diese Seite existiert nicht.",
|
||||
"404.button.home": "Zurück zur Startseite",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.title": "Fehler",
|
||||
"error.description": "Ups!",
|
||||
"error.button.back": "Zurück",
|
||||
"error.msg.default": "Etwas ist schief gelaufen.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -358,6 +358,13 @@ export default {
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
@@ -465,6 +472,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -489,10 +498,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
"signin.button.submit": "Iniciar sesión",
|
||||
"signIn.notify.totp-required.title": "Se requiere autenticación de dos factores",
|
||||
"signIn.notify.totp-required.description": "Por favor ingrese su código de autenticación de dos factores",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.or": "O",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
@@ -51,8 +51,8 @@ export default {
|
||||
"signup.button.submit": "Comencemos",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
"totp.title": "Autenticación TOTP",
|
||||
"totp.button.signIn": "Iniciar sesión",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "¿Olvidaste tu contraseña?",
|
||||
@@ -72,20 +72,20 @@ export default {
|
||||
"account.card.password.title": "Contraseña",
|
||||
"account.card.password.old": "Anterior contraseña",
|
||||
"account.card.password.new": "Nueva contraseña",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.card.password.noPasswordSet": "No tienes una establecida contraseña. Si deseas iniciar sesión con correo electrónico y una contraseña necesitas crear una contraseña.",
|
||||
"account.notify.password.success": "Contraseña cambiada correctamente",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.title": "Inicio de sesión con red social",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.oauth.link": "Vincular",
|
||||
"account.card.oauth.unlink": "Desvincular",
|
||||
"account.card.oauth.unlinked": "Desvinculado",
|
||||
"account.modal.unlink.title": "Desvincular cuenta",
|
||||
"account.modal.unlink.description": "Desvincular tus cuentas sociales puede causar que pierdas tu cuenta si no recuerdas tu nombre de usuario y contraseña.",
|
||||
"account.notify.oauth.unlinked.success": "Desvinculado correctamente",
|
||||
"account.card.security.title": "Seguridad",
|
||||
"account.card.security.totp.enable.description": "Ingrese su contraseña actual para habilitar TOTP",
|
||||
"account.card.security.totp.disable.description": "Ingrese su contraseña actual para deshabilitar TOTP",
|
||||
@@ -214,7 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "No podrás eliminar tus compartidos manualmente ni ver el número de visitas.",
|
||||
"upload.modal.expires.never": "nunca",
|
||||
"upload.modal.expires.never-long": "Nunca Expira",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.expires.error.too-long": "La caducidad excede la fecha de caducidad máxima de {max}.",
|
||||
"upload.modal.link.label": "Enlace",
|
||||
"upload.modal.expires.label": "Expiración",
|
||||
"upload.modal.expires.minute-singular": "Minuto",
|
||||
@@ -264,13 +264,19 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Vista previa no disponible",
|
||||
"share.modal.file-preview.error.not-supported.description": "La vista previa para este tipo de archivo no está disponible. Por favor descargue el archivo para verlo.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Editar {shareId}",
|
||||
"share.edit.append-upload": "Agregar archivo",
|
||||
"share.edit.notify.generic-error": "Ha ocurrido un error mientras se compartía tu archivo.",
|
||||
"share.edit.notify.save-success": "Compartir actualizado correctamente",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuración",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Compartido",
|
||||
"admin.config.category.email": "Correo",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.category.oauth": "Inicio de sesión social",
|
||||
"admin.config.general.app-name": "Nombre de la App",
|
||||
"admin.config.general.app-name.description": "Nombre de la aplicación",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
@@ -302,8 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Si se permite el registro",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Permitir compartir sin iniciar sesión",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Si los usuarios que no han iniciado sesión pueden compartir",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-expiration": "Expiración máxima",
|
||||
"admin.config.share.max-expiration.description": "Expiración máxima para compartir en horas. Establezca en 0 para permitir una expiración ilimitada.",
|
||||
"admin.config.share.max-size": "Tamaño máximo",
|
||||
"admin.config.share.max-size.description": "Tamaño máximo de los archivos, en bytes",
|
||||
"admin.config.share.zip-compression-level": "Nivel de compresión del Zip",
|
||||
@@ -321,22 +327,22 @@ export default {
|
||||
"admin.config.smtp.password": "Contraseña",
|
||||
"admin.config.smtp.password.description": "Contraseña del servidor SMTP",
|
||||
"admin.config.smtp.button.test": "Enviar correo de prueba",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.allow-registration": "Permitir registro",
|
||||
"admin.config.oauth.allow-registration.description": "Permitir a los usuarios registrarse mediante login social",
|
||||
"admin.config.oauth.ignore-totp": "Ignorar TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Ignorar TOTP cuando el usuario utiliza inicio de sesión social",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-enabled.description": "Si el inicio de sesión de GitHub está habilitado",
|
||||
"admin.config.oauth.github-client-id": "ID del Cliente de GitHub",
|
||||
"admin.config.oauth.github-client-id.description": "ID de cliente de la aplicación OAuth de GitHub",
|
||||
"admin.config.oauth.github-client-secret": "Palabra secreta del Cliente de GitHub",
|
||||
"admin.config.oauth.github-client-secret.description": "Secreto de cliente de la aplicación OAuth de GitHub",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.google-enabled.description": "Si el inicio de sesión de Google está habilitado",
|
||||
"admin.config.oauth.google-client-id": "ID del Cliente de Google",
|
||||
"admin.config.oauth.google-client-id.description": "ID de cliente de la aplicación de Google OAuth",
|
||||
"admin.config.oauth.google-client-secret": "Palabra secreta del cliente de Google",
|
||||
"admin.config.oauth.google-client-secret.description": "Secreto del cliente de la aplicación Google OAuth",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Esikatselua ei tuettu",
|
||||
"share.modal.file-preview.error.not-supported.description": "Esikatselua thise tiedostotyypille ei tueta. Ole hyvä ja lataa tiedosto nähdäksesi sen.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Asetukset",
|
||||
"admin.config.category.general": "Yleiset",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Aperçu non supporté",
|
||||
"share.modal.file-preview.error.not-supported.description": "Un aperçu pour ce type de fichier n'est pas pris en charge. Veuillez télécharger le fichier pour le voir.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Modifier {shareId}",
|
||||
"share.edit.append-upload": "Ajouter un fichier",
|
||||
"share.edit.notify.generic-error": "Une erreur est survenue durant le traitement de votre partage.",
|
||||
"share.edit.notify.save-success": "Partage mis à jour avec succès",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Paramètres",
|
||||
"admin.config.category.general": "Général",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Le secret du client de l’application Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Permettre la connexion via Discord.",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "ID du client Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "L’ID du client de l’application OAuth Discord",
|
||||
"admin.config.oauth.discord-client-secret": "Secret du client Discord",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Quelque chose a mal tourné.",
|
||||
"error.msg.access_denied": "Vous avez annulé le processus d’authentification, veuillez réessayer.",
|
||||
"error.msg.expired_token": "Le processus d’authentification a pris trop de temps, veuillez réessayer.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "L’utilisateur associé au compte {0} n’existe pas.",
|
||||
"error.msg.no_email": "Impossible d’obtenir le courriel du compte {0}.",
|
||||
"error.msg.already_linked": "Le compte {0} est déjà associé à un autre compte.",
|
||||
"error.msg.not_linked": "Le compte {0} n’est pas encore associé à compte.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
"signin.button.submit": "サインイン",
|
||||
"signIn.notify.totp-required.title": "二段階認証が必要です",
|
||||
"signIn.notify.totp-required.description": "二段階認証コードを入力してください",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.or": "または",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
@@ -51,8 +51,8 @@ export default {
|
||||
"signup.button.submit": "さあ始めましょう",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
"totp.title": "二段階認証",
|
||||
"totp.button.signIn": "サインイン",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "パスワードを忘れてしまいましたか?",
|
||||
@@ -72,20 +72,20 @@ export default {
|
||||
"account.card.password.title": "パスワード",
|
||||
"account.card.password.old": "現在のパスワード",
|
||||
"account.card.password.new": "新規パスワード",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.card.password.noPasswordSet": "パスワードが設定されていません。メールアドレスとパスワードでサインインしたい場合は、パスワードの設定が必要です。",
|
||||
"account.notify.password.success": "パスワードの変更に成功しました",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.title": "ソーシャルログイン",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.oauth.link": "リンク",
|
||||
"account.card.oauth.unlink": "リンク解除",
|
||||
"account.card.oauth.unlinked": "リンクされていません",
|
||||
"account.modal.unlink.title": "アカウントのリンクを解除",
|
||||
"account.modal.unlink.description": "ソーシャルアカウントのリンクを解除すると、ユーザー名とパスワードを覚えていない場合にアカウントへのアクセスが失われる可能性があります。",
|
||||
"account.notify.oauth.unlinked.success": "リンク解除に成功しました",
|
||||
"account.card.security.title": "セキュリティ",
|
||||
"account.card.security.totp.enable.description": "2段階認証を有効にするため、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.disable.description": "2段階認証を無効にするため、現在のパスワードを入力してください",
|
||||
@@ -214,7 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "共有の手動削除と訪問者カウンターは表示できません。",
|
||||
"upload.modal.expires.never": "永久",
|
||||
"upload.modal.expires.never-long": "期限切れにさせない",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.expires.error.too-long": "設定可能な有効期限を越えています。有効期限の上限は{max} です。",
|
||||
"upload.modal.link.label": "リンク",
|
||||
"upload.modal.expires.label": "有効期限",
|
||||
"upload.modal.expires.minute-singular": "分間",
|
||||
@@ -264,13 +264,19 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "プレビューに対応していません",
|
||||
"share.modal.file-preview.error.not-supported.description": "これらのファイルのプレビューには対応していません。ファイルをダウンロードして、直接確認してください。",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "編集 {shareId}",
|
||||
"share.edit.append-upload": "ファイルを追加",
|
||||
"share.edit.notify.generic-error": "共有の最終処理でエラーが発生しました。",
|
||||
"share.edit.notify.save-success": "共有の更新に成功しました",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "設定",
|
||||
"admin.config.category.general": "一般",
|
||||
"admin.config.category.share": "共有",
|
||||
"admin.config.category.email": "メール",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.category.oauth": "ソーシャルログイン",
|
||||
"admin.config.general.app-name": "アプリ名",
|
||||
"admin.config.general.app-name.description": "アプリの名前",
|
||||
"admin.config.general.app-url": "アプリ名",
|
||||
@@ -302,8 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "登録を許可するかどうかを選択してください。",
|
||||
"admin.config.share.allow-unauthenticated-shares": "ログインしていない状態での共有を許可する",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "ログインしていないユーザーに共有の作成を許可するかどうかを選択してください。",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-expiration": "有効期限の上限",
|
||||
"admin.config.share.max-expiration.description": "共有に設定可能な有効期限の上限を時間単位で設定できます。0を設定すると、有効期限が無制限になります。",
|
||||
"admin.config.share.max-size": "最大ファイルサイズ",
|
||||
"admin.config.share.max-size.description": "最大ファイルサイズ(byte単位)",
|
||||
"admin.config.share.zip-compression-level": "Zip圧縮レベル",
|
||||
@@ -321,58 +327,63 @@ export default {
|
||||
"admin.config.smtp.password": "パスワード",
|
||||
"admin.config.smtp.password.description": "SMTPサーバーのパスワード",
|
||||
"admin.config.smtp.button.test": "テストメールを送信",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.allow-registration": "登録を許可する",
|
||||
"admin.config.oauth.allow-registration.description": "ユーザーにソーシャルアカウント経由での登録を許可します",
|
||||
"admin.config.oauth.ignore-totp": "二段階認証を無視する",
|
||||
"admin.config.oauth.ignore-totp.description": "ソーシャルログイン時に二段階認証を無視するかどうかを設定します",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-enabled.description": "GitHubアカウントを使用したログインを許可するかどうかを設定します",
|
||||
"admin.config.oauth.github-client-id": "GitHub クライアントID",
|
||||
"admin.config.oauth.github-client-id.description": "GitHub OAuthアプリのクライアントID",
|
||||
"admin.config.oauth.github-client-secret": "GitHub クライアントシークレット",
|
||||
"admin.config.oauth.github-client-secret.description": "GitHub OAuthアプリのクライアントシークレット",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.google-enabled.description": "Googleアカウントを使用したログインを許可するかどうかを設定します",
|
||||
"admin.config.oauth.google-client-id": "Google クライアントID",
|
||||
"admin.config.oauth.google-client-id.description": "Google OAuthアプリのクライアントID",
|
||||
"admin.config.oauth.google-client-secret": "Google クライアントシークレット",
|
||||
"admin.config.oauth.google-client-secret.description": "Google OAuthアプリのクライアントシークレット",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Microsoftアカウントを使用したログインを許可するかどうかを設定します",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoftテナント",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Microsoft OAuthアプリのテナントID\ncommon: 個人のMicrosoftアカウントとMicrosoft Entra IDの職場または学校のアカウントを持つユーザーは、アプリケーションにサインインできます。 \norganizations: Microsoft Entra IDからの職場または学校のアカウントを持つユーザーのみがアプリケーションにサインインできます。\nconsumers: 個人のMicrosoftアカウントを持つユーザーのみがアプリケーションにサインインできます。\nMicrosoft Entraテナントのドメイン名またはGUID形式のテナントID: 特定のMicrosoft Entraテナント (職場または学校のアカウントを持つディレクトリメンバーまたはパーソナルMicrosoftアカウントを持つディレクトリゲスト) からのユーザーのみがアプリケーションにサインインできます。",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft クライアントID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Microsoft OAuthアプリのクライアントID",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft クライアントシークレット",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Microsoft OAuthアプリのクライアントシークレット",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-enabled.description": "Discordアカウントを使用したログインを許可するかどうかを設定します",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord クライアントID",
|
||||
"admin.config.oauth.discord-client-id.description": "Discord OAuthアプリのクライアントID",
|
||||
"admin.config.oauth.discord-client-secret": "Discord クライアントシークレット",
|
||||
"admin.config.oauth.discord-client-secret.description": "Discord OAuthアプリのクライアントシークレット",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-enabled.description": "OpenIDアカウントを使用したログインを許可するかどうかを設定します",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "OpenID OAuthアプリのDiscovery URI",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID クライアントID",
|
||||
"admin.config.oauth.oidc-client-id.description": "OpenID OAuthアプリのクライアントID",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID クライアントシークレット",
|
||||
"admin.config.oauth.oidc-client-secret.description": "OpenID OAuthアプリのクライアントシークレット",
|
||||
// 404
|
||||
"404.description": "ページが見つかりません。",
|
||||
"404.button.home": "ホームに戻る",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.title": "エラー",
|
||||
"error.description": "申し訳ありません",
|
||||
"error.button.back": "戻る",
|
||||
"error.msg.default": "問題が発生しました。",
|
||||
"error.msg.access_denied": "認証処理を中止しました、後で再度お試しください。",
|
||||
"error.msg.expired_token": "認証処理に時間がかかりすぎています、後で再度お試しください。",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "この{0} アカウントにリンクしたユーザーが存在しません。",
|
||||
"error.msg.no_email": "この{0} アカウントからメールアドレスを取得出来ません。",
|
||||
"error.msg.already_linked": "この{0} アカウントは、既に別のアカウントにリンクされています。",
|
||||
"error.msg.not_linked": "この{0} アカウントはどのアカウントにもリンクされていません。",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Voorbeeld niet ondersteund",
|
||||
"share.modal.file-preview.error.not-supported.description": "Een voorbeeld voor dit bestandstype wordt niet ondersteund. Download het bestand om het te bekijken.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuratie",
|
||||
"admin.config.category.general": "Algemeen",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Podgląd nie jest obsługiwany",
|
||||
"share.modal.file-preview.error.not-supported.description": "Podgląd dla tego typu pliku nie jest obsługiwany. Pobierz plik, aby go zobaczyć.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edytuj {shareId}",
|
||||
"share.edit.append-upload": "Dołącz plik",
|
||||
"share.edit.notify.generic-error": "W trakcie zakańczania tworzenia udziału wystąpił błąd.",
|
||||
"share.edit.notify.save-success": "Udział zaktualizowany pomyślnie",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Konfiguracja",
|
||||
"admin.config.category.general": "Ogólne",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Sekret klienta aplikacji Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Czy jest włączony login na Discord",
|
||||
"admin.config.oauth.discord-limited-guild": "ID ograniczonego serwera Discord",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Ogranicz logowanie do użytkowników na określonym serwerze. Pozostaw puste, aby wyłączyć.",
|
||||
"admin.config.oauth.discord-client-id": "ID klienta Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "ID klienta aplikacji Discord OAuth",
|
||||
"admin.config.oauth.discord-client-secret": "Sekret klienta Discord",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Coś poszło nie tak.",
|
||||
"error.msg.access_denied": "Anulowałeś proces uwierzytelniania, spróbuj ponownie.",
|
||||
"error.msg.expired_token": "Proces uwierzytelniania trwał zbyt długo, spróbuj ponownie.",
|
||||
"error.msg.invalid_token": "Błąd wewnętrzny",
|
||||
"error.msg.no_user": "Użytkownik powiązany z tym kontem {0} nie istnieje.",
|
||||
"error.msg.no_email": "Nie można pobrać adresu e-mail z tego konta {0}.",
|
||||
"error.msg.already_linked": "To konto {0} zostało już połączone z innym kontem.",
|
||||
"error.msg.not_linked": "To konto {0} nie zostało jeszcze połączone z żadnym kontem.",
|
||||
"error.msg.unverified_account": "To konto {0} nie zostało zweryfikowane, spróbuj ponownie po weryfikacji.",
|
||||
"error.msg.discord_guild_permission_denied": "Nie możesz się zalogować.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -235,7 +235,7 @@ export default {
|
||||
"upload.modal.accordion.email.placeholder": "Insira os destinatários do e-mail",
|
||||
"upload.modal.accordion.email.invalid-email": "Endereço de e-mail inválido",
|
||||
"upload.modal.accordion.security.title": "Opções de segurança",
|
||||
"upload.modal.accordion.security.password.label": "Protecção por senha",
|
||||
"upload.modal.accordion.security.password.label": "Proteção por senha",
|
||||
"upload.modal.accordion.security.password.placeholder": "Sem senha",
|
||||
"upload.modal.accordion.security.max-views.label": "Máximo de visualizações",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "Sem limite",
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Visualização não suportada",
|
||||
"share.modal.file-preview.error.not-supported.description": "Uma visualização para este tipo de arquivo não é suportada. Faça o download do arquivo para visualizá-lo.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Editar {shareId}",
|
||||
"share.edit.append-upload": "Anexar arquivo",
|
||||
"share.edit.notify.generic-error": "Ocorreu um erro ao terminar seu compartilhamento.",
|
||||
"share.edit.notify.save-success": "Compartilhamento atualizado com sucesso",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuração",
|
||||
"admin.config.category.general": "Geral",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret do aplicativo Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Se o login do Discord está habilitado",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "ID do Cliente Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "ID do cliente do aplicativo Discord OAuth",
|
||||
"admin.config.oauth.discord-client-secret": "Segredo do Cliente Discord",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Algo deu errado.",
|
||||
"error.msg.access_denied": "Você cancelou o processo de autenticação, por favor, tente novamente.",
|
||||
"error.msg.expired_token": "O processo de autenticação demorou muito. Por favor, tente novamente.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "O usuário vinculado a esta conta {0} não existe.",
|
||||
"error.msg.no_email": "Não é possível obter o endereço de e-mail desta conta {0}.",
|
||||
"error.msg.already_linked": "Esta conta {0} já está vinculada a outra conta.",
|
||||
"error.msg.not_linked": "Esta conta {0} ainda não foi vinculada a nenhuma conta.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
"signin.button.submit": "Вход",
|
||||
"signIn.notify.totp-required.title": "Требуется двухфакторная аутентификация",
|
||||
"signIn.notify.totp-required.description": "Пожалуйста, введите код Вашей 2-х факторной аутентификации",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.or": "ИЛИ",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
@@ -51,8 +51,8 @@ export default {
|
||||
"signup.button.submit": "Давайте начнём",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
"totp.title": "Авторизация TOTP",
|
||||
"totp.button.signIn": "Войти",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Забыли пароль?",
|
||||
@@ -72,20 +72,20 @@ export default {
|
||||
"account.card.password.title": "Пароль",
|
||||
"account.card.password.old": "Старый пароль",
|
||||
"account.card.password.new": "Новый пароль",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.card.password.noPasswordSet": "У вас не установлен пароль. Если вы хотите войти с помощью электронной почты и пароля, вам необходимо установить пароль.",
|
||||
"account.notify.password.success": "Пароль успешно изменён",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.title": "Авторизация через социальные сети",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.oauth.link": "Подключить",
|
||||
"account.card.oauth.unlink": "Отключить",
|
||||
"account.card.oauth.unlinked": "Отключен",
|
||||
"account.modal.unlink.title": "Отключить связь с учетной записью",
|
||||
"account.modal.unlink.description": "Отключение связи с учетной записью социальных аккаунтов может привести к потере вашей учетной записи, если вы не помните свое имя пользователя и пароль.",
|
||||
"account.notify.oauth.unlinked.success": "Отключение прошло успешно",
|
||||
"account.card.security.title": "Безопасность",
|
||||
"account.card.security.totp.enable.description": "Введите ваш текущий пароль для начала включения TOTP",
|
||||
"account.card.security.totp.disable.description": "Введите ваш текущий пароль, чтобы отключить TOTP",
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
"account.reverseShares.title.empty": "Тут пусто 👀",
|
||||
"account.reverseShares.description.empty": "У вас пока нет обратных загрузок.",
|
||||
// showCreateReverseShareModal.tsx
|
||||
"account.reverseShares.modal.title": "Create reverse share",
|
||||
"account.reverseShares.modal.title": "Создать обратную ссылку на файл",
|
||||
"account.reverseShares.modal.expiration.label": "Истекает",
|
||||
"account.reverseShares.modal.expiration.minute-singular": "Минута",
|
||||
"account.reverseShares.modal.expiration.minute-plural": "Минут(ы)",
|
||||
@@ -214,7 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Вы не сможете удалить свои файлы вручную и просмотреть количество посетителей.",
|
||||
"upload.modal.expires.never": "никогда",
|
||||
"upload.modal.expires.never-long": "Никогда не истекает",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.expires.error.too-long": "Срок действия превышает максимальную дату окончания срока действия {max}.",
|
||||
"upload.modal.link.label": "Ссылка",
|
||||
"upload.modal.expires.label": "Истекает",
|
||||
"upload.modal.expires.minute-singular": "Минута",
|
||||
@@ -264,13 +264,19 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Предпросмотр не поддерживается",
|
||||
"share.modal.file-preview.error.not-supported.description": "Предварительный просмотр этого типа файла не поддерживается. Пожалуйста, скачайте файл для его просмотра.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Редактировать {shareId}",
|
||||
"share.edit.append-upload": "Добавить файл",
|
||||
"share.edit.notify.generic-error": "Произошла ошибка при завершении вашей загрузки.",
|
||||
"share.edit.notify.save-success": "Ссылка на ресурс успешна обновлена",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Конфигурация",
|
||||
"admin.config.category.general": "Общее",
|
||||
"admin.config.category.share": "Загрузки",
|
||||
"admin.config.category.email": "Электронная почта",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.category.oauth": "Авторизация через социальные сети",
|
||||
"admin.config.general.app-name": "Название приложения",
|
||||
"admin.config.general.app-name.description": "Видимое название приложения",
|
||||
"admin.config.general.app-url": "URL-адрес приложения",
|
||||
@@ -302,8 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Разрешена ли регистрация",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Разрешить неавторизованные загрузки",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Могут ли неавторизованные пользователи создавать загрузки",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-expiration": "Максимальная срок действия",
|
||||
"admin.config.share.max-expiration.description": "Максимальный срок действия общего доступа в часах. Установите значение 0, чтобы разрешить неограниченный срок действия.",
|
||||
"admin.config.share.max-size": "Максимальный размер",
|
||||
"admin.config.share.max-size.description": "Максимальный размер файла в байтах",
|
||||
"admin.config.share.zip-compression-level": "Уровень сжатия Zip",
|
||||
@@ -321,58 +327,63 @@ export default {
|
||||
"admin.config.smtp.password": "Пароль",
|
||||
"admin.config.smtp.password.description": "Пароль SMTP-сервера",
|
||||
"admin.config.smtp.button.test": "Отправить тестовое письмо",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.allow-registration": "Разрешить регистрацию",
|
||||
"admin.config.oauth.allow-registration.description": "Разрешить пользователям регистрироваться используя учетные записи социальных сетей",
|
||||
"admin.config.oauth.ignore-totp": "Игнорировать TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Игнорировать TOTP при использовании социальной авторизации",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-enabled.description": "Включен ли логин на GitHub",
|
||||
"admin.config.oauth.github-client-id": "ID клиента GitHub",
|
||||
"admin.config.oauth.github-client-id.description": "ID клиента в приложении GitHub OAuth",
|
||||
"admin.config.oauth.github-client-secret": "Секретный ключ клиента GitHub",
|
||||
"admin.config.oauth.github-client-secret.description": "Секретный ключ клиента в приложении GitHub OAuth",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.google-enabled.description": "Включен ли логин Google на GitHub",
|
||||
"admin.config.oauth.google-client-id": "ID клиента Google",
|
||||
"admin.config.oauth.google-client-id.description": "ID клиента в приложении Google OAuth",
|
||||
"admin.config.oauth.google-client-secret": "Секретный ключ клиента Google",
|
||||
"admin.config.oauth.google-client-secret.description": "Секретный ключ клиента в приложении Google OAuth",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Включен ли логин Microsoft",
|
||||
"admin.config.oauth.microsoft-tenant": "Корпоративный аккаунт Microsoft",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Идентификатор арендатора приложения Microsoft OAuth\ncommon: Пользователи с личным аккаунтом Microsoft и рабочим или учебным аккаунтом от Microsoft Entra ID могут войти в приложение. organizations: Только пользователи с рабочим или учебным аккаунтом от Microsoft Entra ID могут войти в приложение.\nconsumers: Только пользователи с личным аккаунтом Microsoft могут войти в приложение.\nимя домена арендатора Microsoft Entra или идентификатор арендатора в формате GUID: Только пользователи из определенного арендатора Microsoft Entra (участники каталога с рабочим или учебным аккаунтом или гости каталога с личным аккаунтом Microsoft) могут войти в приложение.",
|
||||
"admin.config.oauth.microsoft-client-id": "Идентификатор клиента Microsoft",
|
||||
"admin.config.oauth.microsoft-client-id.description": "ID клиента в приложении Microsoft OAuth",
|
||||
"admin.config.oauth.microsoft-client-secret": "Секретный ключ клиента Microsoft",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Секретный ключ клиента в приложении Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-enabled.description": "Включен ли логин Discord",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "ID клиента Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "ID клиента в приложении Discord OAuth",
|
||||
"admin.config.oauth.discord-client-secret": "Секретный ключ клиента Discord",
|
||||
"admin.config.oauth.discord-client-secret.description": "Секретный ключ клиента в приложении Discord OAuth",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-enabled.description": "Включен ли логин OpenID",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI приложения OpenID OAuth",
|
||||
"admin.config.oauth.oidc-client-id": "ID клиента OpenID",
|
||||
"admin.config.oauth.oidc-client-id.description": "ID клиента в приложении OpenID OAuth",
|
||||
"admin.config.oauth.oidc-client-secret": "Секретный ключ клиента OpenID",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Секретный ключ клиента в приложении OpenID OAuth",
|
||||
// 404
|
||||
"404.description": "Упс, этой страницы не существует.",
|
||||
"404.button.home": "Верните меня домой",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.title": "Ошибка",
|
||||
"error.description": "Что-то пошло не так!",
|
||||
"error.button.back": "Назад",
|
||||
"error.msg.default": "Что-то пошло не так.",
|
||||
"error.msg.access_denied": "Вы отменили процесс аутентификации, пожалуйста, попробуйте еще раз.",
|
||||
"error.msg.expired_token": "Процесс аутентификации занял слишком много времени, пожалуйста, попробуйте еще раз.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "Пользователь связанный с учетной записью {0} не существует.",
|
||||
"error.msg.no_email": "Не удается получить адрес электронной почты от учетной записи {0}.",
|
||||
"error.msg.already_linked": "Эта учетная запись {0} уже привязана к другому аккаунту.",
|
||||
"error.msg.not_linked": "Эта учетная запись {0} ещё не привязана ни к одному аккаунту.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Преглед није подржан",
|
||||
"share.modal.file-preview.error.not-supported.description": "Преглед за овај тип датотеке није подржан. Преузмите датотеку да бисте је видели.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Конфигурација",
|
||||
"admin.config.category.general": "Опште",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Тајна клијента за Microsot OAuth апликацију",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Да ли је пријављивање на Discord омогућено",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord ИД клијента",
|
||||
"admin.config.oauth.discord-client-id.description": "ИД клијента Discord OAuth апликације",
|
||||
"admin.config.oauth.discord-client-secret": "Discord клијент тајна",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Нешто је пошло наопако.",
|
||||
"error.msg.access_denied": "Отказали сте процес аутентификације, покушајте поново.",
|
||||
"error.msg.expired_token": "Процес аутентификације је трајао предуго, покушајте поново.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "Корисник повезан са овим {0} налогом не постоји.",
|
||||
"error.msg.no_email": "Не могу да добијем адресу е-поште са овог {0} налога.",
|
||||
"error.msg.already_linked": "Овај {0} налог је већ повезан са другим налогом.",
|
||||
"error.msg.not_linked": "Овај {0} налог још увек није повезан ни са једним налогом.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
416
frontend/src/i18n/translations/sv-SE.ts
Normal file
416
frontend/src/i18n/translations/sv-SE.ts
Normal file
@@ -0,0 +1,416 @@
|
||||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Ladda upp",
|
||||
"navbar.signin": "Logga in",
|
||||
"navbar.home": "Startsida",
|
||||
"navbar.signup": "Skapa konto",
|
||||
"navbar.links.shares": "Mina delningar",
|
||||
"navbar.links.reverse": "Omvända delningar",
|
||||
"navbar.avatar.account": "Mitt konto",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Logga ut",
|
||||
// END navbar
|
||||
// /
|
||||
"home.title": "En <h>egen</h> fildelningsplattform.",
|
||||
"home.description": "Vill du verkligen lägga dina personliga filer hos en tredje part som WeTransfer?",
|
||||
"home.bullet.a.name": "Lokalt installerad",
|
||||
"home.bullet.a.description": "Hosta Pingvin Share på din egen maskin.",
|
||||
"home.bullet.b.name": "Sekretess",
|
||||
"home.bullet.b.description": "Dina filer är dina filer och bör aldrig hamna i händerna på tredje part.",
|
||||
"home.bullet.c.name": "Ingen irriterande filstorleksbegränsning",
|
||||
"home.bullet.c.description": "Ladda upp så stora filer som du vill. Endast din hårddisk kommer att vara din gräns.",
|
||||
"home.button.start": "Kom igång",
|
||||
"home.button.source": "Källkod",
|
||||
// END /
|
||||
// /auth/signin
|
||||
"signin.title": "Välkommen tillbaka",
|
||||
"signin.description": "Har du inget konto än?",
|
||||
"signin.button.signup": "Skapa konto",
|
||||
"signin.input.email-or-username": "E-post eller användarnamn",
|
||||
"signin.input.email-or-username.placeholder": "Din e-postadress eller ditt användarnamn",
|
||||
"signin.input.password": "Lösenord",
|
||||
"signin.input.password.placeholder": "Lösenord",
|
||||
"signin.button.submit": "Logga in",
|
||||
"signIn.notify.totp-required.title": "Tvåfaktorsautentisering krävs",
|
||||
"signIn.notify.totp-required.description": "Vänligen ange din tvåfaktorsautentiseringskod",
|
||||
"signIn.oauth.or": "ELLER",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Skapa ett konto",
|
||||
"signup.description": "Har du redan ett konto?",
|
||||
"signup.button.signin": "Logga in",
|
||||
"signup.input.username": "Användarnamn",
|
||||
"signup.input.username.placeholder": "Ditt användarnamn",
|
||||
"signup.input.email": "E-post",
|
||||
"signup.input.email.placeholder": "Din e-post",
|
||||
"signup.button.submit": "Kom igång",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP-autentisering",
|
||||
"totp.button.signIn": "Logga in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Glömt ditt lösenord?",
|
||||
"resetPassword.description": "Ange din e-postadress för att återställa ditt lösenord.",
|
||||
"resetPassword.notify.success": "Ett e-postmeddelande har skickats till dig med en länk för att återställa lösenordet.",
|
||||
"resetPassword.button.back": "Tillbaka till inloggningssidan",
|
||||
"resetPassword.text.resetPassword": "Återställ lösenord",
|
||||
"resetPassword.text.enterNewPassword": "Ange ditt nya lösenord",
|
||||
"resetPassword.input.password": "Nytt lösenord",
|
||||
"resetPassword.notify.passwordReset": "Ditt lösenord har återställts.",
|
||||
// /account
|
||||
"account.title": "Mitt konto",
|
||||
"account.card.info.title": "Kontoinformation",
|
||||
"account.card.info.username": "Användarnamn",
|
||||
"account.card.info.email": "E-post",
|
||||
"account.notify.info.success": "Kontot uppdaterades",
|
||||
"account.card.password.title": "Lösenord",
|
||||
"account.card.password.old": "Gammalt lösenord",
|
||||
"account.card.password.new": "Nytt lösenord",
|
||||
"account.card.password.noPasswordSet": "Du har inget lösenord. Om du vill logga in med e-post och lösenord måste du ange ett lösenord.",
|
||||
"account.notify.password.success": "Lösenordet har ändrats",
|
||||
"account.card.oauth.title": "Inloggning via sociala nätverk",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Länka",
|
||||
"account.card.oauth.unlink": "Avlänka",
|
||||
"account.card.oauth.unlinked": "Avlänkad",
|
||||
"account.modal.unlink.title": "Avlänka konto",
|
||||
"account.modal.unlink.description": "Om du kopplar bort dina sociala konton kan det leda till att du förlorar ditt konto om du inte kommer ihåg ditt användarnamn och lösenord.",
|
||||
"account.notify.oauth.unlinked.success": "Avlänkning utförd",
|
||||
"account.card.security.title": "Säkerhet",
|
||||
"account.card.security.totp.enable.description": "Ange ditt nuvarande lösenord för att aktivera TOTP",
|
||||
"account.card.security.totp.disable.description": "Ange ditt lösenord för att inaktivera TOTP",
|
||||
"account.card.security.totp.button.start": "Start",
|
||||
"account.modal.totp.title": "Aktivera TOTP",
|
||||
"account.modal.totp.step1": "Steg 1: Lägg till din autentiserare",
|
||||
"account.modal.totp.step2": "Steg 2: Bekräfta din kod",
|
||||
"account.modal.totp.enterManually": "Ange manuellt",
|
||||
"account.modal.totp.code": "Kod",
|
||||
"account.modal.totp.clickToCopy": "Klicka för att kopiera",
|
||||
"account.modal.totp.verify": "Verifiera",
|
||||
"account.notify.totp.disable": "TOTP har inaktiverats",
|
||||
"account.notify.totp.enable": "TOTP aktiverat",
|
||||
"account.card.language.title": "Språk",
|
||||
"account.card.language.description": "Projektet är översatt av gemenskapen. Vissa översättningar kan vara ofullständiga.",
|
||||
"account.card.color.title": "Färgschema",
|
||||
// ThemeSwitcher.tsx
|
||||
"account.theme.dark": "Mörk",
|
||||
"account.theme.light": "Ljus",
|
||||
"account.theme.system": "System",
|
||||
"account.button.delete": "Ta bort konto",
|
||||
"account.modal.delete.title": "Ta bort konto",
|
||||
"account.modal.delete.description": "Vill du verkligen ta bort ditt konto inklusive alla dina aktiva delningar?",
|
||||
// END /account
|
||||
// /account/shares
|
||||
"account.shares.title": "Mina delningar",
|
||||
"account.shares.title.empty": "Här var det tomt 👀",
|
||||
"account.shares.description.empty": "Du har inga delningar.",
|
||||
"account.shares.button.create": "Skapa en",
|
||||
"account.shares.info.title": "Information om delning",
|
||||
"account.shares.table.id": "ID",
|
||||
"account.shares.table.name": "Namn",
|
||||
"account.shares.table.description": "Beskrivning",
|
||||
"account.shares.table.visitors": "Besökare",
|
||||
"account.shares.table.expiresAt": "Förfaller den",
|
||||
"account.shares.table.createdAt": "Skapad den",
|
||||
"account.shares.table.size": "Storlek",
|
||||
"account.shares.modal.share-informations": "Information om delning",
|
||||
"account.shares.modal.share-link": "Delningslänk",
|
||||
"account.shares.modal.delete.title": "Ta bort delning {share}",
|
||||
"account.shares.modal.delete.description": "Vill du verkligen ta bort denna delning?",
|
||||
// END /account/shares
|
||||
// /account/reverseShares
|
||||
"account.reverseShares.title": "Omvända delningar",
|
||||
"account.reverseShares.description": "En omvänd delning gör att du kan generera en unik URL som tillåter externa användare att skapa en delning.",
|
||||
"account.reverseShares.title.empty": "Här var det tomt 👀",
|
||||
"account.reverseShares.description.empty": "Du har inga omvända delningar.",
|
||||
// showCreateReverseShareModal.tsx
|
||||
"account.reverseShares.modal.title": "Skapa omvänd delning",
|
||||
"account.reverseShares.modal.expiration.label": "Förfaller",
|
||||
"account.reverseShares.modal.expiration.minute-singular": "Minut",
|
||||
"account.reverseShares.modal.expiration.minute-plural": "Minuter",
|
||||
"account.reverseShares.modal.expiration.hour-singular": "Timme",
|
||||
"account.reverseShares.modal.expiration.hour-plural": "Timmar",
|
||||
"account.reverseShares.modal.expiration.day-singular": "Dag",
|
||||
"account.reverseShares.modal.expiration.day-plural": "Dagar",
|
||||
"account.reverseShares.modal.expiration.week-singular": "Vecka",
|
||||
"account.reverseShares.modal.expiration.week-plural": "Veckor",
|
||||
"account.reverseShares.modal.expiration.month-singular": "Månad",
|
||||
"account.reverseShares.modal.expiration.month-plural": "Månader",
|
||||
"account.reverseShares.modal.expiration.year-singular": "År",
|
||||
"account.reverseShares.modal.expiration.year-plural": "År",
|
||||
"account.reverseShares.modal.max-size.label": "Max storlek på delning",
|
||||
"account.reverseShares.modal.send-email": "Skicka e-postavisering",
|
||||
"account.reverseShares.modal.send-email.description": "Skicka ett e-postmeddelande när en delning skapas med denna länk för omvänd delning.",
|
||||
"account.reverseShares.modal.max-use.label": "Maxanvändningar",
|
||||
"account.reverseShares.modal.max-use.description": "Den maximala mängden gånger denna URL kan användas för att skapa en delning.",
|
||||
"account.reverseShare.never-expires": "Denna omvända delning kommer aldrig att förfalla.",
|
||||
"account.reverseShare.expires-on": "Denna sammanlagda delning löper ut på {expiration}.",
|
||||
"account.reverseShares.table.no-shares": "Inga delningar har skapats ännu",
|
||||
"account.reverseShares.table.count.singular": "delning",
|
||||
"account.reverseShares.table.count.plural": "delningar",
|
||||
"account.reverseShares.table.shares": "Delningar",
|
||||
"account.reverseShares.table.remaining": "Återstående användningar",
|
||||
"account.reverseShares.table.max-size": "Max storlek på delning",
|
||||
"account.reverseShares.table.expires": "Förfaller den",
|
||||
"account.reverseShares.modal.reverse-share-link": "Omvänd delningslänk",
|
||||
"account.reverseShares.modal.delete.title": "Ta bort omvänd delning",
|
||||
"account.reverseShares.modal.delete.description": "Vill du verkligen ta bort denna omvänd delning? Om du gör det, kommer de tillhörande delningarna också att raderas.",
|
||||
// END /account/reverseShares
|
||||
// /admin
|
||||
"admin.title": "Administration",
|
||||
"admin.button.users": "Användarhantering",
|
||||
"admin.button.config": "Konfiguration",
|
||||
"admin.version": "Version",
|
||||
// END /admin
|
||||
// /admin/users
|
||||
"admin.users.title": "Användarhantering",
|
||||
"admin.users.table.username": "Användarnamn",
|
||||
"admin.users.table.email": "E-post",
|
||||
"admin.users.table.admin": "Administratör",
|
||||
"admin.users.edit.update.title": "Uppdatera användare {username}",
|
||||
"admin.users.edit.update.admin-privileges": "Administratörsbehörigheter",
|
||||
"admin.users.edit.update.change-password.title": "Ändra lösenord",
|
||||
"admin.users.edit.update.change-password.field": "Nytt lösenord",
|
||||
"admin.users.edit.update.change-password.button": "Spara nytt lösenord",
|
||||
"admin.users.edit.update.notify.password.success": "Lösenordet har ändrats",
|
||||
"admin.users.edit.delete.title": "Ta bort användare {username}",
|
||||
"admin.users.edit.delete.description": "Vill du verkligen ta bort denna användare och alla deras delningar?",
|
||||
// showCreateUserModal.tsx
|
||||
"admin.users.modal.create.title": "Skapa användare",
|
||||
"admin.users.modal.create.username": "Användarnamn",
|
||||
"admin.users.modal.create.email": "E-post",
|
||||
"admin.users.modal.create.password": "Lösenord",
|
||||
"admin.users.modal.create.manual-password": "Sätt lösenord manuellt",
|
||||
"admin.users.modal.create.manual-password.description": "Om den inte är markerad kommer användaren att få ett e-postmeddelande med en länk för att ange lösenordet.",
|
||||
"admin.users.modal.create.admin": "Administratörsbehörigheter",
|
||||
"admin.users.modal.create.admin.description": "Om detta markeras kommer användaren att kunna komma åt administratörspanelen.",
|
||||
// END /admin/users
|
||||
// /upload
|
||||
"upload.title": "Ladda upp",
|
||||
"upload.notify.generic-error": "Ett fel uppstod när din delning skulle slutföras.",
|
||||
"upload.notify.count-failed": "{count} filer kunde inte laddas upp. Försöker igen.",
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Ladda upp filer",
|
||||
"upload.dropzone.description": "Drag och släpp filer här för att starta din delning. Vi kan endast acceptera filer som är mindre än {maxSize} totalt.",
|
||||
"upload.dropzone.notify.file-too-big": "Dina filer överskrider den maximala storleken på {maxSize}.",
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Namn",
|
||||
"upload.filelist.size": "Storlek",
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Skapa delning",
|
||||
"upload.modal.link.error.invalid": "Kan endast innehålla bokstäver, siffror, understreck och bindestreck",
|
||||
"upload.modal.link.error.taken": "Denna länk används redan",
|
||||
"upload.modal.not-signed-in": "Du är inte inloggad",
|
||||
"upload.modal.not-signed-in-description": "Du kommer inte att kunna ta bort din delning manuellt och visa antalet besökare.",
|
||||
"upload.modal.expires.never": "aldrig",
|
||||
"upload.modal.expires.never-long": "Upphör aldrig",
|
||||
"upload.modal.expires.error.too-long": "Förfallodatum överskrider maximalt utgångsdatum för {max}.",
|
||||
"upload.modal.link.label": "Länk",
|
||||
"upload.modal.expires.label": "Förfaller",
|
||||
"upload.modal.expires.minute-singular": "Minut",
|
||||
"upload.modal.expires.minute-plural": "Minuter",
|
||||
"upload.modal.expires.hour-singular": "Timme",
|
||||
"upload.modal.expires.hour-plural": "Timmar",
|
||||
"upload.modal.expires.day-singular": "Dag",
|
||||
"upload.modal.expires.day-plural": "Dagar",
|
||||
"upload.modal.expires.week-singular": "Vecka",
|
||||
"upload.modal.expires.week-plural": "Veckor",
|
||||
"upload.modal.expires.month-singular": "Månad",
|
||||
"upload.modal.expires.month-plural": "Månader",
|
||||
"upload.modal.expires.year-singular": "År",
|
||||
"upload.modal.expires.year-plural": "År",
|
||||
"upload.modal.accordion.description.title": "Beskrivning",
|
||||
"upload.modal.accordion.description.placeholder": "Anteckning till mottagare av denna delning",
|
||||
"upload.modal.accordion.email.title": "E-postmottagare",
|
||||
"upload.modal.accordion.email.placeholder": "Ange e-postmottagare",
|
||||
"upload.modal.accordion.email.invalid-email": "Ogiltig e-postadress",
|
||||
"upload.modal.accordion.security.title": "Säkerhetsalternativ",
|
||||
"upload.modal.accordion.security.password.label": "Lösenordsskydd",
|
||||
"upload.modal.accordion.security.password.placeholder": "Inget lösenord",
|
||||
"upload.modal.accordion.security.max-views.label": "Max antal visningar",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "Ingen gräns",
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "Denna delning kommer aldrig att upphöra.",
|
||||
"upload.modal.completed.expires-on": "Denna delning upphör att gälla {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Delning redo",
|
||||
// END /upload
|
||||
// /share/[id]
|
||||
"share.title": "Delning {shareId}",
|
||||
"share.description": "Titta vad jag har delat med dig!",
|
||||
"share.error.visitor-limit-exceeded.title": "Besökargränsen överskriden",
|
||||
"share.error.visitor-limit-exceeded.description": "Gränsen för antalet besökare för denna delning har överskridits.",
|
||||
"share.error.removed.title": "Delning borttagen",
|
||||
"share.error.not-found.title": "Delningen hittades inte",
|
||||
"share.error.not-found.description": "Delningen du letar efter existerar inte.",
|
||||
"share.modal.password.title": "Lösenord krävs",
|
||||
"share.modal.password.description": "För att komma åt denna delning, vänligen ange lösenordet för delningen.",
|
||||
"share.modal.password": "Lösenord",
|
||||
"share.modal.error.invalid-password": "Ogiltigt lösenord",
|
||||
"share.button.download-all": "Ladda ner allt",
|
||||
"share.notify.download-all-preparing": "Delningen förbereds. Försök igen om några minuter.",
|
||||
"share.modal.file-link": "Fillänk",
|
||||
"share.table.name": "Namn",
|
||||
"share.table.size": "Storlek",
|
||||
"share.modal.file-preview.error.not-supported.title": "Förhandsgranskning stöds ej",
|
||||
"share.modal.file-preview.error.not-supported.description": "En förhandsvisning för filtypen stöds inte. Ladda ner filen för att se den.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Redigera {shareId}",
|
||||
"share.edit.append-upload": "Lägg till fil",
|
||||
"share.edit.notify.generic-error": "Ett fel uppstod när din delning skulle slutföras.",
|
||||
"share.edit.notify.save-success": "Delningen har uppdaterats",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Konfiguration",
|
||||
"admin.config.category.general": "Allmänt",
|
||||
"admin.config.category.share": "Delning",
|
||||
"admin.config.category.email": "E-post",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Inloggning via sociala nätverk",
|
||||
"admin.config.general.app-name": "Appnamn",
|
||||
"admin.config.general.app-name.description": "Namn på applikationen",
|
||||
"admin.config.general.app-url": "Appens URL",
|
||||
"admin.config.general.app-url.description": "På vilken URL Pingvin Share finns",
|
||||
"admin.config.general.show-home-page": "Visa startsidan",
|
||||
"admin.config.general.show-home-page.description": "Om du vill visa startsidan",
|
||||
"admin.config.general.logo": "Logotyp",
|
||||
"admin.config.general.logo.description": "Ändra din logotyp genom att ladda upp en ny bild. Bilden måste vara en PNG och bör ha formatet 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Välj bild",
|
||||
"admin.config.email.enable-share-email-recipients": "Aktivera e-postmottagare för delning",
|
||||
"admin.config.email.enable-share-email-recipients.description": "Om du vill tillåta e-post till delningsmottagare. Aktivera endast detta om du har aktiverat 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": "Meddelande som skickas till delningens mottagare. Tillgängliga variabler:\n {creator} - Användarnamnet för skaparen av delningen\n {shareUrl} - URL för delningen\n {desc} - Beskrivningen av delningen\n {expires} - Förfallodatumet för delningen\n Variablerna kommer att ersättas med det faktiska värdet.",
|
||||
"admin.config.email.reverse-share-subject": "Omvänd delning ämne",
|
||||
"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": "Återställ lösenord meddelande",
|
||||
"admin.config.email.reset-password-message.description": "Meddelande som skickas när en användare begär en lösenordsåterställning. {url} kommer att ersättas med länken för lösenordsåterställningen.",
|
||||
"admin.config.email.invite-subject": "Inbjudan ämne",
|
||||
"admin.config.email.invite-subject.description": "Ämne i e-postmeddelandet som skickas när en administratör bjuder in en användare.",
|
||||
"admin.config.email.invite-message": "Inbjudningsmeddelanden",
|
||||
"admin.config.email.invite-message.description": "Meddelande som skickas när en administratör bjuder in en användare. {url} kommer att ersättas med inbjudningsadressen och {password} med lösenordet.",
|
||||
"admin.config.share.allow-registration": "Tillåt registrering",
|
||||
"admin.config.share.allow-registration.description": "Om registrering är tillåten",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Tillåt oautentiserade delningar",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Om oautentiserade användare kan skapa delningar",
|
||||
"admin.config.share.max-expiration": "Max utgångsdatum",
|
||||
"admin.config.share.max-expiration.description": "Max längd innan en delning förfaller i timmar. Sätt till 0 för att tillåta obegränsad förfallotid.",
|
||||
"admin.config.share.max-size": "Max storlek",
|
||||
"admin.config.share.max-size.description": "Maximal storlek för delning i bytes",
|
||||
"admin.config.share.zip-compression-level": "Komprimeringsnivå för zip",
|
||||
"admin.config.share.zip-compression-level.description": "Adjust the level to balance between file size and compression speed. Valid values range from 0 to 9, with 0 being no compression and 9 being maximum compression. ",
|
||||
"admin.config.smtp.enabled": "Aktiverad",
|
||||
"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": "Adress",
|
||||
"admin.config.smtp.host.description": "Adress för SMTP-servern",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port för SMTP-servern",
|
||||
"admin.config.smtp.email": "E-post",
|
||||
"admin.config.smtp.email.description": "E-postadress som e-postmeddelanden skickas från",
|
||||
"admin.config.smtp.username": "Användarnamn",
|
||||
"admin.config.smtp.username.description": "Användarnamn för SMTP-servern",
|
||||
"admin.config.smtp.password": "Lösenord",
|
||||
"admin.config.smtp.password.description": "Lösenord för SMTP-servern",
|
||||
"admin.config.smtp.button.test": "Skicka testmeddelande",
|
||||
"admin.config.oauth.allow-registration": "Tillåt registrering",
|
||||
"admin.config.oauth.allow-registration.description": "Tillåt användare att registrera sig via social inloggning",
|
||||
"admin.config.oauth.ignore-totp": "Ignorera TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Om du vill ignorera TOTP när användaren använder social inloggning",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Om GitHub-inloggning är aktiverad",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client-ID för GitHub OAuth appen",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret för GitHub OAuth appen",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Om Google-inloggning är aktiverad",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client-ID för Google OAuth appen",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret för Google OAuth appen",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Hoppsan den här sidan finns inte.",
|
||||
"404.button.home": "Ta mig tillbaka hem",
|
||||
// error
|
||||
"error.title": "Fel",
|
||||
"error.description": "Hoppsan!",
|
||||
"error.button.back": "Gå tillbaka",
|
||||
"error.msg.default": "Någonting gick fel.",
|
||||
"error.msg.access_denied": "Du avbröt autentiseringsprocessen, försök igen.",
|
||||
"error.msg.expired_token": "Autentiseringsprocessen tog för lång tid, försök igen.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "Användare som är länkad till detta {0} konto finns inte.",
|
||||
"error.msg.no_email": "Kan inte hämta e-postadress från detta {0} konto.",
|
||||
"error.msg.already_linked": "Detta {0} konto är redan länkat till ett annat konto.",
|
||||
"error.msg.not_linked": "Detta {0} konto har ännu inte länkat till något konto.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Spara",
|
||||
"common.button.create": "Skapa",
|
||||
"common.button.submit": "Skicka",
|
||||
"common.button.delete": "Ta bort",
|
||||
"common.button.cancel": "Avbryt",
|
||||
"common.button.confirm": "Bekräfta",
|
||||
"common.button.disable": "Inaktivera",
|
||||
"common.button.share": "Delning",
|
||||
"common.button.generate": "Generera",
|
||||
"common.button.done": "Klar",
|
||||
"common.text.link": "Länk",
|
||||
"common.text.or": "eller",
|
||||
"common.button.go-back": "Gå tillbaka",
|
||||
"common.notify.copied": "Din länk har kopierats till urklipp",
|
||||
"common.success": "Slutförd",
|
||||
"common.error": "Fel",
|
||||
"common.error.unknown": "Ett okänt fel har uppstått",
|
||||
"common.error.invalid-email": "Ogiltig e-postadress",
|
||||
"common.error.too-short": "Måste minst vara {length} tecken långt",
|
||||
"common.error.too-long": "Får högst innehålla {length} tecken",
|
||||
"common.error.exact-length": "Måste vara exakt {length} tecken långt",
|
||||
"common.error.invalid-number": "Måste vara ett tal",
|
||||
"common.error.field-required": "Obligatoriskt fält"
|
||||
};
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "ไม่รองรับการแสดงตัวอย่าง",
|
||||
"share.modal.file-preview.error.not-supported.description": "ไม่รองรับการแสดงตัวอย่างสำหรับไฟล์ประเภทนี้ โปรดดาวน์โหลดไฟล์เพื่อดู",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "การตั้งค่า",
|
||||
"admin.config.category.general": "ทั่วไป",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -264,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "该文件类型不支持预览",
|
||||
"share.modal.file-preview.error.not-supported.description": "该文件类型不支持预览,请下载后打开查看",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "编辑 {shareId}",
|
||||
"share.edit.append-upload": "追加文件",
|
||||
"share.edit.notify.generic-error": "保存共享的过程中发生了错误",
|
||||
"share.edit.notify.save-success": "共享已更新成功",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "配置管理",
|
||||
"admin.config.category.general": "通用",
|
||||
@@ -347,6 +353,8 @@ export default {
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
@@ -369,10 +377,13 @@ export default {
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
416
frontend/src/i18n/translations/zh-TW.ts
Normal file
416
frontend/src/i18n/translations/zh-TW.ts
Normal file
@@ -0,0 +1,416 @@
|
||||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "上傳",
|
||||
"navbar.signin": "登入",
|
||||
"navbar.home": "首頁",
|
||||
"navbar.signup": "註冊",
|
||||
"navbar.links.shares": "我的分享",
|
||||
"navbar.links.reverse": "檔案請求",
|
||||
"navbar.avatar.account": "帳號設定",
|
||||
"navbar.avatar.admin": "管理",
|
||||
"navbar.avatar.signout": "登出",
|
||||
// END navbar
|
||||
// /
|
||||
"home.title": "<h>自建</h> 檔案分享平台!",
|
||||
"home.description": "您真的放心把檔案交到第三方檔案平台手中嗎?",
|
||||
"home.bullet.a.name": "完全自建",
|
||||
"home.bullet.a.description": "輕松使用私有服務器搭建檔案分享平台",
|
||||
"home.bullet.b.name": "完全隱私",
|
||||
"home.bullet.b.description": "您的檔案只屬於您!不需將它放到第三方檔案平台",
|
||||
"home.bullet.c.name": "完全無限",
|
||||
"home.bullet.c.description": "想上傳多大都可以,更需要擔心的是您的存儲空間容量",
|
||||
"home.button.start": "開始使用",
|
||||
"home.button.source": "原始碼",
|
||||
// END /
|
||||
// /auth/signin
|
||||
"signin.title": "歡迎回來",
|
||||
"signin.description": "還沒有帳號嗎?請",
|
||||
"signin.button.signup": "註冊",
|
||||
"signin.input.email-or-username": "電子郵件或使用者名稱",
|
||||
"signin.input.email-or-username.placeholder": "請輸入電子郵件或使用者名稱",
|
||||
"signin.input.password": "密碼",
|
||||
"signin.input.password.placeholder": "請輸入密碼",
|
||||
"signin.button.submit": "登入",
|
||||
"signIn.notify.totp-required.title": "請繼續兩步驗證",
|
||||
"signIn.notify.totp-required.description": "請輸入一次性驗證碼",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "建立帳號",
|
||||
"signup.description": "已經有帳號了?請",
|
||||
"signup.button.signin": "登入",
|
||||
"signup.input.username": "使用者名稱",
|
||||
"signup.input.username.placeholder": "請輸入使用者名稱",
|
||||
"signup.input.email": "電子郵件",
|
||||
"signup.input.email.placeholder": "請輸入電子郵件",
|
||||
"signup.button.submit": "註冊",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "忘記密碼?",
|
||||
"resetPassword.description": "請輸入Email以讓系統寄送重置密碼郵件",
|
||||
"resetPassword.notify.success": "一封包含密碼重置地址的郵件已發送到您的Email",
|
||||
"resetPassword.button.back": "返回登入頁面",
|
||||
"resetPassword.text.resetPassword": "重置密碼",
|
||||
"resetPassword.text.enterNewPassword": "請輸入新密碼",
|
||||
"resetPassword.input.password": "新密碼",
|
||||
"resetPassword.notify.passwordReset": "密碼更新成功!",
|
||||
// /account
|
||||
"account.title": "我的帳號",
|
||||
"account.card.info.title": "帳號資訊",
|
||||
"account.card.info.username": "使用者名稱",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "帳號資訊更新成功!",
|
||||
"account.card.password.title": "密碼",
|
||||
"account.card.password.old": "舊密碼",
|
||||
"account.card.password.new": "新密碼",
|
||||
"account.card.password.noPasswordSet": "並未設定密碼,若希望使用E-mail與密碼進行登入則必須要設定密碼。",
|
||||
"account.notify.password.success": "密碼更改成功!",
|
||||
"account.card.oauth.title": "第三方登入",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "聯結",
|
||||
"account.card.oauth.unlink": "取消聯結",
|
||||
"account.card.oauth.unlinked": "已取消聯結",
|
||||
"account.modal.unlink.title": "取消聯結第三方登入",
|
||||
"account.modal.unlink.description": "若不記得使用者名稱與密碼,取消聯結第三方登入可能會使您無法登入。",
|
||||
"account.notify.oauth.unlinked.success": "已完成取消聯結",
|
||||
"account.card.security.title": "安控",
|
||||
"account.card.security.totp.enable.description": "請輸入當前密碼開啟兩步驗證",
|
||||
"account.card.security.totp.disable.description": "請輸入當前密碼關閉兩步驗證",
|
||||
"account.card.security.totp.button.start": "開啟",
|
||||
"account.modal.totp.title": "開啟兩步驗證",
|
||||
"account.modal.totp.step1": "第一步:添加驗證應用程式 Authenticator",
|
||||
"account.modal.totp.step2": "第二步:輸入一次性驗證碼",
|
||||
"account.modal.totp.enterManually": "手動輸入",
|
||||
"account.modal.totp.code": "驗證碼",
|
||||
"account.modal.totp.clickToCopy": "複製",
|
||||
"account.modal.totp.verify": "確認",
|
||||
"account.notify.totp.disable": "成功關閉兩步驗證!",
|
||||
"account.notify.totp.enable": "成功開啟兩步驗證!",
|
||||
"account.card.language.title": "語言",
|
||||
"account.card.language.description": "此專案由社群進行翻譯,部分語言可能尚未完成翻譯。",
|
||||
"account.card.color.title": "顏色外觀",
|
||||
// ThemeSwitcher.tsx
|
||||
"account.theme.dark": "深色模式",
|
||||
"account.theme.light": "淺色模式",
|
||||
"account.theme.system": "比照系統",
|
||||
"account.button.delete": "刪除帳號",
|
||||
"account.modal.delete.title": "刪除帳號",
|
||||
"account.modal.delete.description": "您真的想刪除您的帳號,並刪除所有的分享嗎?",
|
||||
// END /account
|
||||
// /account/shares
|
||||
"account.shares.title": "我的分享",
|
||||
"account.shares.title.empty": "這裡空空如也 👀",
|
||||
"account.shares.description.empty": "您沒有建立任何分享",
|
||||
"account.shares.button.create": "建立分享",
|
||||
"account.shares.info.title": "分享資訊",
|
||||
"account.shares.table.id": "ID",
|
||||
"account.shares.table.name": "分享代號",
|
||||
"account.shares.table.description": "描述",
|
||||
"account.shares.table.visitors": "造訪次數",
|
||||
"account.shares.table.expiresAt": "過期時間",
|
||||
"account.shares.table.createdAt": "建立時間",
|
||||
"account.shares.table.size": "檔案大小",
|
||||
"account.shares.modal.share-informations": "分享資訊",
|
||||
"account.shares.modal.share-link": "分享聯結",
|
||||
"account.shares.modal.delete.title": "刪除 {share}",
|
||||
"account.shares.modal.delete.description": "您真的想刪除這個分享嗎?",
|
||||
// END /account/shares
|
||||
// /account/reverseShares
|
||||
"account.reverseShares.title": "檔案請求",
|
||||
"account.reverseShares.description": "檔案請求允許您建立一個特定的上船畫面,以便外部使用者與您分享檔案",
|
||||
"account.reverseShares.title.empty": "這里空空如也 👀",
|
||||
"account.reverseShares.description.empty": "您沒有建立任何檔案請求",
|
||||
// showCreateReverseShareModal.tsx
|
||||
"account.reverseShares.modal.title": "建立檔案請求",
|
||||
"account.reverseShares.modal.expiration.label": "過期時間",
|
||||
"account.reverseShares.modal.expiration.minute-singular": "分鐘",
|
||||
"account.reverseShares.modal.expiration.minute-plural": "分鐘",
|
||||
"account.reverseShares.modal.expiration.hour-singular": "小時",
|
||||
"account.reverseShares.modal.expiration.hour-plural": "小時",
|
||||
"account.reverseShares.modal.expiration.day-singular": "天",
|
||||
"account.reverseShares.modal.expiration.day-plural": "天",
|
||||
"account.reverseShares.modal.expiration.week-singular": "周",
|
||||
"account.reverseShares.modal.expiration.week-plural": "周",
|
||||
"account.reverseShares.modal.expiration.month-singular": "月",
|
||||
"account.reverseShares.modal.expiration.month-plural": "月",
|
||||
"account.reverseShares.modal.expiration.year-singular": "年",
|
||||
"account.reverseShares.modal.expiration.year-plural": "年",
|
||||
"account.reverseShares.modal.max-size.label": "上傳大小上限",
|
||||
"account.reverseShares.modal.send-email": "發送Email提醒",
|
||||
"account.reverseShares.modal.send-email.description": "當這個檔案請求聯結被用於分享時,發送Email提醒",
|
||||
"account.reverseShares.modal.max-use.label": "最大使用次數",
|
||||
"account.reverseShares.modal.max-use.description": "這個檔案請求聯結可被用於建立分享的最大使用次數",
|
||||
"account.reverseShare.never-expires": "這個檔案請求永不過期",
|
||||
"account.reverseShare.expires-on": "這個檔案請求將過期於 {expiration}",
|
||||
"account.reverseShares.table.no-shares": "當前沒有建立任何分享",
|
||||
"account.reverseShares.table.count.singular": "分享",
|
||||
"account.reverseShares.table.count.plural": "分享",
|
||||
"account.reverseShares.table.shares": "分享",
|
||||
"account.reverseShares.table.remaining": "剩餘使用次數",
|
||||
"account.reverseShares.table.max-size": "上傳大小上限",
|
||||
"account.reverseShares.table.expires": "過期時間",
|
||||
"account.reverseShares.modal.reverse-share-link": "檔案請求聯結",
|
||||
"account.reverseShares.modal.delete.title": "刪除檔案請求聯結",
|
||||
"account.reverseShares.modal.delete.description": "您真的想刪除此檔案請求聯結嗎?聯結下所有關聯的檔案都將被刪除",
|
||||
// END /account/reverseShares
|
||||
// /admin
|
||||
"admin.title": "管理",
|
||||
"admin.button.users": "使用者管理",
|
||||
"admin.button.config": "配置管理",
|
||||
"admin.version": "版本",
|
||||
// END /admin
|
||||
// /admin/users
|
||||
"admin.users.title": "使用者管理",
|
||||
"admin.users.table.username": "使用者名稱",
|
||||
"admin.users.table.email": "Email",
|
||||
"admin.users.table.admin": "管理員",
|
||||
"admin.users.edit.update.title": "更新使用者 {username}",
|
||||
"admin.users.edit.update.admin-privileges": "管理員",
|
||||
"admin.users.edit.update.change-password.title": "更改密碼",
|
||||
"admin.users.edit.update.change-password.field": "新密碼",
|
||||
"admin.users.edit.update.change-password.button": "保存新密碼",
|
||||
"admin.users.edit.update.notify.password.success": "密碼更新成功!",
|
||||
"admin.users.edit.delete.title": "刪除使用者 {username}",
|
||||
"admin.users.edit.delete.description": "您真的想刪除這個帳號,並刪除該使用者所有的分享嗎?",
|
||||
// showCreateUserModal.tsx
|
||||
"admin.users.modal.create.title": "建立使用者",
|
||||
"admin.users.modal.create.username": "使用者名稱",
|
||||
"admin.users.modal.create.email": "Email",
|
||||
"admin.users.modal.create.password": "密碼",
|
||||
"admin.users.modal.create.manual-password": "手動設置密碼",
|
||||
"admin.users.modal.create.manual-password.description": "如果不勾選,使用者將會收到一封Email來設置他們的密碼",
|
||||
"admin.users.modal.create.admin": "管理員",
|
||||
"admin.users.modal.create.admin.description": "如果勾選,使用者將能查看管理員面板",
|
||||
// END /admin/users
|
||||
// /upload
|
||||
"upload.title": "上傳",
|
||||
"upload.notify.generic-error": "建立分享的過程中發生了錯誤",
|
||||
"upload.notify.count-failed": "{count} 檔案上傳失敗,請重試",
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "上傳檔案",
|
||||
"upload.dropzone.description": "將檔案拖曳至此以上傳,檔案大小不能超過上限 {maxSize}",
|
||||
"upload.dropzone.notify.file-too-big": "您的檔案超過了最大上傳限制 {maxSize}",
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "檔案名稱",
|
||||
"upload.filelist.size": "檔案大小",
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "建立分享",
|
||||
"upload.modal.link.error.invalid": "只能包括字母,數字,下劃線(_),和橫線(-)",
|
||||
"upload.modal.link.error.taken": "這個分享代號已經存在了",
|
||||
"upload.modal.not-signed-in": "當前沒有登入",
|
||||
"upload.modal.not-signed-in-description": "您將不能刪除您的分享或查看查看次數",
|
||||
"upload.modal.expires.never": "永不",
|
||||
"upload.modal.expires.never-long": "永不過期",
|
||||
"upload.modal.expires.error.too-long": "過期時間超出了系統上限設定 {max}。",
|
||||
"upload.modal.link.label": "分享聯結",
|
||||
"upload.modal.expires.label": "過期時間",
|
||||
"upload.modal.expires.minute-singular": "分鐘",
|
||||
"upload.modal.expires.minute-plural": "分鐘",
|
||||
"upload.modal.expires.hour-singular": "小時",
|
||||
"upload.modal.expires.hour-plural": "小時",
|
||||
"upload.modal.expires.day-singular": "天",
|
||||
"upload.modal.expires.day-plural": "天",
|
||||
"upload.modal.expires.week-singular": "周",
|
||||
"upload.modal.expires.week-plural": "周",
|
||||
"upload.modal.expires.month-singular": "月",
|
||||
"upload.modal.expires.month-plural": "月",
|
||||
"upload.modal.expires.year-singular": "年",
|
||||
"upload.modal.expires.year-plural": "年",
|
||||
"upload.modal.accordion.description.title": "描述",
|
||||
"upload.modal.accordion.description.placeholder": "分享檔案備註資訊",
|
||||
"upload.modal.accordion.email.title": "Email提醒",
|
||||
"upload.modal.accordion.email.placeholder": "收件人Email地址",
|
||||
"upload.modal.accordion.email.invalid-email": "Email地址不可用",
|
||||
"upload.modal.accordion.security.title": "安控",
|
||||
"upload.modal.accordion.security.password.label": "密碼保護",
|
||||
"upload.modal.accordion.security.password.placeholder": "無密碼",
|
||||
"upload.modal.accordion.security.max-views.label": "最大查看次數",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "無限",
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "這個分享永不過期",
|
||||
"upload.modal.completed.expires-on": "這個分享將過期於 {expiration}。",
|
||||
"upload.modal.completed.share-ready": "分享建立完畢",
|
||||
// END /upload
|
||||
// /share/[id]
|
||||
"share.title": "分享 {shareId}",
|
||||
"share.description": "瞧瞧我給您分享了些什麽!",
|
||||
"share.error.visitor-limit-exceeded.title": "查看次數達到上限",
|
||||
"share.error.visitor-limit-exceeded.description": "查看次數達到上限",
|
||||
"share.error.removed.title": "分享已刪除",
|
||||
"share.error.not-found.title": "分享未找到",
|
||||
"share.error.not-found.description": "分享檔案遺失了",
|
||||
"share.modal.password.title": "需要密碼",
|
||||
"share.modal.password.description": "請輸入密碼來查看此分享",
|
||||
"share.modal.password": "密碼",
|
||||
"share.modal.error.invalid-password": "密碼錯誤",
|
||||
"share.button.download-all": "全部下載",
|
||||
"share.notify.download-all-preparing": "正在處理中,請稍等片刻",
|
||||
"share.modal.file-link": "檔案聯結",
|
||||
"share.table.name": "檔案名稱",
|
||||
"share.table.size": "檔案大小",
|
||||
"share.modal.file-preview.error.not-supported.title": "該檔案類型不支持預覽",
|
||||
"share.modal.file-preview.error.not-supported.description": "該檔案類型不支持預覽,請下載後打開查看",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "編輯 {shareId}",
|
||||
"share.edit.append-upload": "追加檔案",
|
||||
"share.edit.notify.generic-error": "保存分享的過程中發生了錯誤",
|
||||
"share.edit.notify.save-success": "分享已更新成功",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "配置管理",
|
||||
"admin.config.category.general": "通用",
|
||||
"admin.config.category.share": "分享",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "第三方登入",
|
||||
"admin.config.general.app-name": "App 名稱",
|
||||
"admin.config.general.app-name.description": "這個 App 的名稱",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description": "Pingvin Share 的 URL",
|
||||
"admin.config.general.show-home-page": "顯示首頁",
|
||||
"admin.config.general.show-home-page.description": "是否顯示首頁",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description": "上傳個性化 Logo,圖片必須是長寬比 1:1 的 PNG 格式",
|
||||
"admin.config.general.logo.placeholder": "選擇圖片",
|
||||
"admin.config.email.enable-share-email-recipients": "開啟分享Email通知",
|
||||
"admin.config.email.enable-share-email-recipients.description": "是否允許通過Email通知發送分享資訊,需進行 SMTP 設定後此選項才有效",
|
||||
"admin.config.email.share-recipients-subject": "分享Email通知主題",
|
||||
"admin.config.email.share-recipients-subject.description": "發送分享Email通知的主題",
|
||||
"admin.config.email.share-recipients-message": "分享Email通知內容",
|
||||
"admin.config.email.share-recipients-message.description": "發送到接收者的分享Email通知具體內容。可使用的參數有:\n {creator} - 分享建立者\n {shareUrl} - 分享聯結\n {desc} - 分享描述\n {expires} - 分享過期時間\n 這些參數會被實際的值所替代",
|
||||
"admin.config.email.reverse-share-subject": "檔案請求Email通知主題",
|
||||
"admin.config.email.reverse-share-subject.description": "當有人使用了您的檔案請求聯結時,發送的檔案請求通知主題",
|
||||
"admin.config.email.reverse-share-message": "檔案請求Email通知內容",
|
||||
"admin.config.email.reverse-share-message.description": "當有人使用了您的檔案請求聯結時,發送的通知內容。{shareUrl} 會被建立者的使用者名稱和分享聯結代替",
|
||||
"admin.config.email.reset-password-subject": "重置密碼Email通知主題",
|
||||
"admin.config.email.reset-password-subject.description": "當使用者發動重置密碼時,發送的重置密碼通知主題",
|
||||
"admin.config.email.reset-password-message": "重置密碼Email通知內容",
|
||||
"admin.config.email.reset-password-message.description": "當使用者發動重置密碼時,重置密碼通知內容。{url} 會被重置密碼聯結代替",
|
||||
"admin.config.email.invite-subject": "邀請Email通知主題",
|
||||
"admin.config.email.invite-subject.description": "當管理員邀請使用者時,發送的邀請通知主題",
|
||||
"admin.config.email.invite-message": "邀請Email通知內容",
|
||||
"admin.config.email.invite-message.description": "當管理員邀請使用者時,發送的邀請通知內容。{url} 會被邀請聯結代替,{password} 會被密碼代替",
|
||||
"admin.config.share.allow-registration": "允許註冊",
|
||||
"admin.config.share.allow-registration.description": "是否允許註冊",
|
||||
"admin.config.share.allow-unauthenticated-shares": "是否允許未登入進行分享",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "是否允許未登入的使用者建立分享",
|
||||
"admin.config.share.max-expiration": "最大過期時間",
|
||||
"admin.config.share.max-expiration.description": "最大過期時間(小時),若設定為0則為永不過期",
|
||||
"admin.config.share.max-size": "最大檔案上限",
|
||||
"admin.config.share.max-size.description": "最大檔案上限,單位 bytes (1GB=1024MB=1048576KB=1073741824bytes)",
|
||||
"admin.config.share.zip-compression-level": "Zip 壓縮等級",
|
||||
"admin.config.share.zip-compression-level.description": "調整壓縮等級以平衡檔案大小和壓縮速度。 有效值範圍從 0 到 9,其中 0 表示無壓縮,9 表示最大壓縮",
|
||||
"admin.config.smtp.enabled": "啟用",
|
||||
"admin.config.smtp.enabled.description": "是否開啟 SMTP,需輸入Host、Port、發送郵箱、使用者名稱和密碼後才有作用",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "SMTP Host",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "SMTP Port",
|
||||
"admin.config.smtp.email": "發送郵箱",
|
||||
"admin.config.smtp.email.description": "發送郵箱地址",
|
||||
"admin.config.smtp.username": "使用者名稱",
|
||||
"admin.config.smtp.username.description": "SMTP 主機使用者名稱",
|
||||
"admin.config.smtp.password": "密碼",
|
||||
"admin.config.smtp.password.description": "SMTP 主機密碼",
|
||||
"admin.config.smtp.button.test": "發送測試Email",
|
||||
"admin.config.oauth.allow-registration": "允許註冊",
|
||||
"admin.config.oauth.allow-registration.description": "允許使用者以第三方登入註冊",
|
||||
"admin.config.oauth.ignore-totp": "略過 TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "當使用者使用第三方登入時,略過 TOTP 驗證",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "啟用 Github 登入",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "GitHub OAuth 的 Client ID",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "GitHub OAuth 的 Client secret",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "啟用 Google 登入",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Google OAuth 的 Client ID",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Google OAuth 的 Client secret",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "啟用 Microsoft 登入",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Microsoft OAuth 的 Tenant ID\ncommon: 擁有 Microsoft Entra ID 的個人 Microsoft 帳號和工作或學校帳號的使用者可以登入該應用程式。\norganizations: 擁有 Microsoft Entra ID 的工作或學校帳號的使用者才能登入應用程式。\nconsumers: 只有擁有個人 Microsoft 帳號的使用者才能登入該應用程式。\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: 只有來自特定 Microsoft Entra 租戶的使用者(具有工作或學校帳號的目錄成員或具有個人 Microsoft 帳號的目錄成員)才能登入應用程式。",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Microsoft OAuth 的 Client ID",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Microsoft OAuth 的 Client secret",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "啟用 Discord 登入",
|
||||
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
|
||||
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Discord OAuth 的 Client ID",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Discord OAuth 的 Client secret",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "啟用 OpenID 登入",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "OpenID OAuth 的 Discovery URI",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "OpenID OAuth 的 Client ID",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "OpenID OAuth 的 Client secret",
|
||||
// 404
|
||||
"404.description": "查無此頁",
|
||||
"404.button.home": "返回主頁",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "返回上一頁",
|
||||
"error.msg.default": "發生預期外的問題。",
|
||||
"error.msg.access_denied": "您取消了身分驗證,請重試。",
|
||||
"error.msg.expired_token": "身分驗證過程逾時,請重試。",
|
||||
"error.msg.invalid_token": "Internal Error",
|
||||
"error.msg.no_user": "與此 {0} 帳號關聯的使用者不存在。",
|
||||
"error.msg.no_email": "無法從此 {0} 帳號取得Email地址。",
|
||||
"error.msg.already_linked": "此 {0} 帳號已與另一個帳號關聯。",
|
||||
"error.msg.not_linked": "此 {0} 帳號尚未關聯到任何帳號。",
|
||||
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.",
|
||||
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "儲存",
|
||||
"common.button.create": "建立",
|
||||
"common.button.submit": "送出",
|
||||
"common.button.delete": "刪除",
|
||||
"common.button.cancel": "取消",
|
||||
"common.button.confirm": "確認",
|
||||
"common.button.disable": "關閉",
|
||||
"common.button.share": "分享",
|
||||
"common.button.generate": "自動產生",
|
||||
"common.button.done": "完成",
|
||||
"common.text.link": "聯結",
|
||||
"common.text.or": "或",
|
||||
"common.button.go-back": "返回",
|
||||
"common.notify.copied": "已複製到剪貼簿",
|
||||
"common.success": "成功",
|
||||
"common.error": "錯誤",
|
||||
"common.error.unknown": "發生預期外錯誤",
|
||||
"common.error.invalid-email": "Email地址不可用",
|
||||
"common.error.too-short": "必須不少於 {length} 個字元",
|
||||
"common.error.too-long": "必須不超過 {length} 個字元",
|
||||
"common.error.exact-length": "必須為 {length} 個字元",
|
||||
"common.error.invalid-number": "必須為數字",
|
||||
"common.error.field-required": "必填"
|
||||
};
|
||||
@@ -16,7 +16,7 @@ import { useModals } from "@mantine/modals";
|
||||
import moment from "moment";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbInfoCircle, TbLink, TbTrash } from "react-icons/tb";
|
||||
import { TbEdit, TbInfoCircle, TbLink, TbTrash } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import showShareInformationsModal from "../../components/account/showShareInformationsModal";
|
||||
@@ -110,6 +110,11 @@ const MyShares = () => {
|
||||
</td>
|
||||
<td>
|
||||
<Group position="right">
|
||||
<Link href={`/share/${share.id}/edit`}>
|
||||
<ActionIcon color="orange" variant="light" size={25}>
|
||||
<TbEdit />
|
||||
</ActionIcon>
|
||||
</Link>
|
||||
<ActionIcon
|
||||
color="blue"
|
||||
variant="light"
|
||||
|
||||
65
frontend/src/pages/share/[shareId]/edit.tsx
Normal file
65
frontend/src/pages/share/[shareId]/edit.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { LoadingOverlay } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
import showErrorModal from "../../../components/share/showErrorModal";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { Share as ShareType } from "../../../types/share.type";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import EditableUpload from "../../../components/upload/EditableUpload";
|
||||
import Meta from "../../../components/Meta";
|
||||
|
||||
export function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: { shareId: context.params!.shareId },
|
||||
};
|
||||
}
|
||||
|
||||
const Share = ({ shareId }: { shareId: string }) => {
|
||||
const t = useTranslate();
|
||||
const modals = useModals();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [share, setShare] = useState<ShareType>();
|
||||
|
||||
useEffect(() => {
|
||||
shareService
|
||||
.getFromOwner(shareId)
|
||||
.then((share) => {
|
||||
setShare(share);
|
||||
})
|
||||
.catch((e) => {
|
||||
const { error } = e.response.data;
|
||||
if (e.response.status == 404) {
|
||||
if (error == "share_removed") {
|
||||
showErrorModal(
|
||||
modals,
|
||||
t("share.error.removed.title"),
|
||||
e.response.data.message,
|
||||
);
|
||||
} else {
|
||||
showErrorModal(
|
||||
modals,
|
||||
t("share.error.not-found.title"),
|
||||
t("share.error.not-found.description"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showErrorModal(modals, t("common.error"), t("common.error.unknown"));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) return <LoadingOverlay visible />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("share.edit.title", { shareId })} />
|
||||
<EditableUpload shareId={shareId} files={share?.files || []} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Share;
|
||||
@@ -202,7 +202,9 @@ const Upload = ({
|
||||
showCreateUploadModalCallback={showCreateUploadModalCallback}
|
||||
isUploading={isUploading}
|
||||
/>
|
||||
{files.length > 0 && <FileList files={files} setFiles={setFiles} />}
|
||||
{files.length > 0 && (
|
||||
<FileList<FileUpload> files={files} setFiles={setFiles} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,10 +19,18 @@ const completeShare = async (id: string) => {
|
||||
return (await api.post(`shares/${id}/complete`)).data;
|
||||
};
|
||||
|
||||
const revertComplete = async (id: string) => {
|
||||
return (await api.delete(`shares/${id}/complete`)).data;
|
||||
};
|
||||
|
||||
const get = async (id: string): Promise<Share> => {
|
||||
return (await api.get(`shares/${id}`)).data;
|
||||
};
|
||||
|
||||
const getFromOwner = async (id: string): Promise<Share> => {
|
||||
return (await api.get(`shares/${id}/from-owner`)).data;
|
||||
};
|
||||
|
||||
const getMetaData = async (id: string): Promise<ShareMetaData> => {
|
||||
return (await api.get(`shares/${id}/metaData`)).data;
|
||||
};
|
||||
@@ -63,6 +71,10 @@ const downloadFile = async (shareId: string, fileId: string) => {
|
||||
window.location.href = `${window.location.origin}/api/shares/${shareId}/files/${fileId}`;
|
||||
};
|
||||
|
||||
const removeFile = async (shareId: string, fileId: string) => {
|
||||
await api.delete(`shares/${shareId}/files/${fileId}`);
|
||||
};
|
||||
|
||||
const uploadFile = async (
|
||||
shareId: string,
|
||||
readerEvent: ProgressEvent<FileReader>,
|
||||
@@ -121,14 +133,17 @@ const removeReverseShare = async (id: string) => {
|
||||
export default {
|
||||
create,
|
||||
completeShare,
|
||||
revertComplete,
|
||||
getShareToken,
|
||||
get,
|
||||
getFromOwner,
|
||||
remove,
|
||||
getMetaData,
|
||||
doesFileSupportPreview,
|
||||
getMyShares,
|
||||
isShareIdAvailable,
|
||||
downloadFile,
|
||||
removeFile,
|
||||
uploadFile,
|
||||
setReverseShare,
|
||||
createReverseShare,
|
||||
|
||||
@@ -7,3 +7,5 @@ export type FileMetaData = {
|
||||
name: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
export type FileListItem = FileUpload | (FileMetaData & { deleted?: boolean });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "0.19.2",
|
||||
"version": "0.21.0",
|
||||
"scripts": {
|
||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||
|
||||
Reference in New Issue
Block a user