From bddb87b9b3ec5426a3c7a14a96caf2eb45b93ff7 Mon Sep 17 00:00:00 2001 From: iUnstable0 Date: Thu, 17 Aug 2023 19:54:26 +0700 Subject: [PATCH] feat(localization): Added thai language (#231) * feat(localization): Added Thai translation * Formatted --------- Co-authored-by: Elias Schneider --- backend/src/auth/auth.controller.ts | 26 +- backend/src/auth/auth.service.ts | 12 +- backend/src/auth/authTotp.service.ts | 6 +- .../src/auth/decorator/getUser.decorator.ts | 2 +- backend/src/auth/strategy/jwt.strategy.ts | 5 +- backend/src/clamscan/clamscan.service.ts | 4 +- backend/src/config/config.controller.ts | 10 +- backend/src/config/config.service.ts | 6 +- backend/src/config/dto/adminConfig.dto.ts | 2 +- backend/src/config/dto/config.dto.ts | 2 +- backend/src/config/logo.service.ts | 2 +- backend/src/email/email.service.ts | 18 +- backend/src/file/file.controller.ts | 8 +- backend/src/file/file.service.ts | 16 +- backend/src/file/guard/fileSecurity.guard.ts | 6 +- backend/src/jobs/jobs.service.ts | 6 +- .../dto/reverseShareTokenWithShares.ts | 2 +- .../reverseShare/reverseShare.controller.ts | 6 +- .../src/reverseShare/reverseShare.service.ts | 8 +- backend/src/share/dto/myShare.dto.ts | 2 +- backend/src/share/dto/share.dto.ts | 2 +- backend/src/share/guard/createShare.guard.ts | 4 +- backend/src/share/guard/shareOwner.guard.ts | 2 +- .../src/share/guard/shareSecurity.guard.ts | 8 +- .../share/guard/shareTokenSecurity.guard.ts | 2 +- backend/src/share/share.controller.ts | 10 +- backend/src/share/share.service.ts | 20 +- backend/src/user/dto/updateOwnUser.dto.ts | 2 +- backend/src/user/dto/user.dto.ts | 2 +- backend/src/user/user.controller.ts | 4 +- backend/src/user/user.service.ts | 6 +- frontend/src/components/share/FilePreview.tsx | 1 + .../upload/modals/showCreateUploadModal.tsx | 2 +- frontend/src/i18n/locales.ts | 6 + frontend/src/i18n/translations/th-TH.ts | 438 ++++++++++++++++++ 35 files changed, 553 insertions(+), 105 deletions(-) create mode 100644 frontend/src/i18n/translations/th-TH.ts diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index bb9c10d9..9f37f18b 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -33,14 +33,14 @@ export class AuthController { constructor( private authService: AuthService, private authTotpService: AuthTotpService, - private config: ConfigService + private config: ConfigService, ) {} @Post("signUp") @Throttle(10, 5 * 60) async signUp( @Body() dto: AuthRegisterDTO, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, ) { if (!this.config.get("share.allowRegistration")) throw new ForbiddenException("Registration is not allowed"); @@ -50,7 +50,7 @@ export class AuthController { response = this.addTokensToResponse( response, result.refreshToken, - result.accessToken + result.accessToken, ); return result; @@ -61,7 +61,7 @@ export class AuthController { @HttpCode(200) async signIn( @Body() dto: AuthSignInDTO, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, ) { const result = await this.authService.signIn(dto); @@ -69,7 +69,7 @@ export class AuthController { response = this.addTokensToResponse( response, result.refreshToken, - result.accessToken + result.accessToken, ); } @@ -81,14 +81,14 @@ export class AuthController { @HttpCode(200) async signInTotp( @Body() dto: AuthSignInTotpDTO, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, ) { const result = await this.authTotpService.signInTotp(dto); response = this.addTokensToResponse( response, result.refreshToken, - result.accessToken + result.accessToken, ); return new TokenDTO().from(result); @@ -113,12 +113,12 @@ export class AuthController { async updatePassword( @GetUser() user: User, @Res({ passthrough: true }) response: Response, - @Body() dto: UpdatePasswordDTO + @Body() dto: UpdatePasswordDTO, ) { const result = await this.authService.updatePassword( user, dto.oldPassword, - dto.password + dto.password, ); response = this.addTokensToResponse(response, result.refreshToken); @@ -129,12 +129,12 @@ export class AuthController { @HttpCode(200) async refreshAccessToken( @Req() request: Request, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, ) { if (!request.cookies.refresh_token) throw new UnauthorizedException(); const accessToken = await this.authService.refreshAccessToken( - request.cookies.refresh_token + request.cookies.refresh_token, ); response = this.addTokensToResponse(response, undefined, accessToken); return new TokenDTO().from({ accessToken }); @@ -143,7 +143,7 @@ export class AuthController { @Post("signOut") async signOut( @Req() request: Request, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, ) { await this.authService.signOut(request.cookies.access_token); response.cookie("access_token", "accessToken", { maxAge: -1 }); @@ -176,7 +176,7 @@ export class AuthController { private addTokensToResponse( response: Response, refreshToken?: string, - accessToken?: string + accessToken?: string, ) { if (accessToken) response.cookie("access_token", accessToken, { sameSite: "lax" }); diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 0e5470ca..27d2edc0 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -21,7 +21,7 @@ export class AuthService { private prisma: PrismaService, private jwtService: JwtService, private config: ConfigService, - private emailService: EmailService + private emailService: EmailService, ) {} async signUp(dto: AuthRegisterDTO) { @@ -39,7 +39,7 @@ export class AuthService { }); const { refreshToken, refreshTokenId } = await this.createRefreshToken( - user.id + user.id, ); const accessToken = await this.createAccessToken(user, refreshTokenId); @@ -49,7 +49,7 @@ export class AuthService { if (e.code == "P2002") { const duplicatedField: string = e.meta.target[0]; throw new BadRequestException( - `A user with this ${duplicatedField} already exists` + `A user with this ${duplicatedField} already exists`, ); } } @@ -78,7 +78,7 @@ export class AuthService { } const { refreshToken, refreshTokenId } = await this.createRefreshToken( - user.id + user.id, ); const accessToken = await this.createAccessToken(user, refreshTokenId); @@ -158,7 +158,7 @@ export class AuthService { { expiresIn: "15min", secret: this.config.get("internal.jwtSecret"), - } + }, ); } @@ -189,7 +189,7 @@ export class AuthService { return this.createAccessToken( refreshTokenMetaData.user, - refreshTokenMetaData.id + refreshTokenMetaData.id, ); } diff --git a/backend/src/auth/authTotp.service.ts b/backend/src/auth/authTotp.service.ts index 9759a37c..3760ab8a 100644 --- a/backend/src/auth/authTotp.service.ts +++ b/backend/src/auth/authTotp.service.ts @@ -18,7 +18,7 @@ export class AuthTotpService { constructor( private prisma: PrismaService, private authService: AuthService, - private config: ConfigService + private config: ConfigService, ) {} async signInTotp(dto: AuthSignInTotpDTO) { @@ -72,7 +72,7 @@ export class AuthTotpService { await this.authService.createRefreshToken(user.id); const accessToken = await this.authService.createAccessToken( user, - refreshTokenId + refreshTokenId, ); return { accessToken, refreshToken }; @@ -98,7 +98,7 @@ export class AuthTotpService { const otpURL = totp.keyuri( user.username || user.email, this.config.get("general.appName"), - secret + secret, ); await this.prisma.user.update({ diff --git a/backend/src/auth/decorator/getUser.decorator.ts b/backend/src/auth/decorator/getUser.decorator.ts index 3765475c..3fdd7cea 100644 --- a/backend/src/auth/decorator/getUser.decorator.ts +++ b/backend/src/auth/decorator/getUser.decorator.ts @@ -5,5 +5,5 @@ export const GetUser = createParamDecorator( const request = ctx.switchToHttp().getRequest(); const user = request.user; return data ? user?.[data] : user; - } + }, ); diff --git a/backend/src/auth/strategy/jwt.strategy.ts b/backend/src/auth/strategy/jwt.strategy.ts index b167af08..81edf912 100644 --- a/backend/src/auth/strategy/jwt.strategy.ts +++ b/backend/src/auth/strategy/jwt.strategy.ts @@ -8,7 +8,10 @@ import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(config: ConfigService, private prisma: PrismaService) { + constructor( + config: ConfigService, + private prisma: PrismaService, + ) { config.get("internal.jwtSecret"); super({ jwtFromRequest: JwtStrategy.extractJWT, diff --git a/backend/src/clamscan/clamscan.service.ts b/backend/src/clamscan/clamscan.service.ts index e2ed5949..1b5786a7 100644 --- a/backend/src/clamscan/clamscan.service.ts +++ b/backend/src/clamscan/clamscan.service.ts @@ -19,7 +19,7 @@ export class ClamScanService { constructor( private fileService: FileService, - private prisma: PrismaService + private prisma: PrismaService, ) {} private ClamScan: Promise = new NodeClam() @@ -81,7 +81,7 @@ export class ClamScanService { }); this.logger.warn( - `Share ${shareId} deleted because it contained ${infectedFiles.length} malicious file(s)` + `Share ${shareId} deleted because it contained ${infectedFiles.length} malicious file(s)`, ); } } diff --git a/backend/src/config/config.controller.ts b/backend/src/config/config.controller.ts index 683c0bf9..37afdab9 100644 --- a/backend/src/config/config.controller.ts +++ b/backend/src/config/config.controller.ts @@ -28,7 +28,7 @@ export class ConfigController { constructor( private configService: ConfigService, private logoService: LogoService, - private emailService: EmailService + private emailService: EmailService, ) {} @Get() @@ -41,7 +41,7 @@ export class ConfigController { @UseGuards(JwtGuard, AdministratorGuard) async getByCategory(@Param("category") category: string) { return new AdminConfigDTO().fromList( - await this.configService.getByCategory(category) + await this.configService.getByCategory(category), ); } @@ -49,7 +49,7 @@ export class ConfigController { @UseGuards(JwtGuard, AdministratorGuard) async updateMany(@Body() data: UpdateConfigDTO[]) { return new AdminConfigDTO().fromList( - await this.configService.updateMany(data) + await this.configService.updateMany(data), ); } @@ -66,9 +66,9 @@ export class ConfigController { @UploadedFile( new ParseFilePipe({ validators: [new FileTypeValidator({ fileType: "image/png" })], - }) + }), ) - file: Express.Multer.File + file: Express.Multer.File, ) { return await this.logoService.create(file.buffer); } diff --git a/backend/src/config/config.service.ts b/backend/src/config/config.service.ts index d1eba311..431a127e 100644 --- a/backend/src/config/config.service.ts +++ b/backend/src/config/config.service.ts @@ -11,12 +11,12 @@ import { PrismaService } from "src/prisma/prisma.service"; export class ConfigService { constructor( @Inject("CONFIG_VARIABLES") private configVariables: Config[], - private prisma: PrismaService + private prisma: PrismaService, ) {} get(key: `${string}.${string}`): any { const configVariable = this.configVariables.filter( - (variable) => `${variable.category}.${variable.name}` == key + (variable) => `${variable.category}.${variable.name}` == key, )[0]; if (!configVariable) throw new Error(`Config variable ${key} not found`); @@ -89,7 +89,7 @@ export class ConfigService { configVariable.type != "text" ) { throw new BadRequestException( - `Config variable must be of type ${configVariable.type}` + `Config variable must be of type ${configVariable.type}`, ); } diff --git a/backend/src/config/dto/adminConfig.dto.ts b/backend/src/config/dto/adminConfig.dto.ts index 5527dd27..c54b36f1 100644 --- a/backend/src/config/dto/adminConfig.dto.ts +++ b/backend/src/config/dto/adminConfig.dto.ts @@ -25,7 +25,7 @@ export class AdminConfigDTO extends ConfigDTO { fromList(partial: Partial[]) { return partial.map((part) => - plainToClass(AdminConfigDTO, part, { excludeExtraneousValues: true }) + plainToClass(AdminConfigDTO, part, { excludeExtraneousValues: true }), ); } } diff --git a/backend/src/config/dto/config.dto.ts b/backend/src/config/dto/config.dto.ts index 1c2a779f..301fe8c5 100644 --- a/backend/src/config/dto/config.dto.ts +++ b/backend/src/config/dto/config.dto.ts @@ -12,7 +12,7 @@ export class ConfigDTO { fromList(partial: Partial[]) { return partial.map((part) => - plainToClass(ConfigDTO, part, { excludeExtraneousValues: true }) + plainToClass(ConfigDTO, part, { excludeExtraneousValues: true }), ); } } diff --git a/backend/src/config/logo.service.ts b/backend/src/config/logo.service.ts index d9234a4f..1fdadcaf 100644 --- a/backend/src/config/logo.service.ts +++ b/backend/src/config/logo.service.ts @@ -25,7 +25,7 @@ export class LogoService { fs.promises.writeFile( `${IMAGES_PATH}/icons/icon-${size}x${size}.png`, resized, - "binary" + "binary", ); } } diff --git a/backend/src/email/email.service.ts b/backend/src/email/email.service.ts index 988dccb7..fb5179b9 100644 --- a/backend/src/email/email.service.ts +++ b/backend/src/email/email.service.ts @@ -32,7 +32,7 @@ export class EmailService { await this.getTransporter() .sendMail({ from: `"${this.config.get("general.appName")}" <${this.config.get( - "smtp.email" + "smtp.email", )}>`, to: email, subject, @@ -49,7 +49,7 @@ export class EmailService { shareId: string, creator?: User, description?: string, - expiration?: Date + expiration?: Date, ) { if (!this.config.get("email.enableShareEmailRecipients")) throw new InternalServerErrorException("Email service disabled"); @@ -69,8 +69,8 @@ export class EmailService { "{expires}", moment(expiration).unix() != 0 ? moment(expiration).fromNow() - : "in: never" - ) + : "in: never", + ), ); } @@ -83,13 +83,13 @@ export class EmailService { this.config .get("email.reverseShareMessage") .replaceAll("\\n", "\n") - .replaceAll("{shareUrl}", shareUrl) + .replaceAll("{shareUrl}", shareUrl), ); } async sendResetPasswordEmail(recipientEmail: string, token: string) { const resetPasswordUrl = `${this.config.get( - "general.appUrl" + "general.appUrl", )}/auth/resetPassword/${token}`; await this.sendMail( @@ -98,7 +98,7 @@ export class EmailService { this.config .get("email.resetPasswordMessage") .replaceAll("\\n", "\n") - .replaceAll("{url}", resetPasswordUrl) + .replaceAll("{url}", resetPasswordUrl), ); } @@ -111,7 +111,7 @@ export class EmailService { this.config .get("email.inviteMessage") .replaceAll("{url}", loginUrl) - .replaceAll("{password}", password) + .replaceAll("{password}", password), ); } @@ -119,7 +119,7 @@ export class EmailService { await this.getTransporter() .sendMail({ from: `"${this.config.get("general.appName")}" <${this.config.get( - "smtp.email" + "smtp.email", )}>`, to: recipientEmail, subject: "Test email", diff --git a/backend/src/file/file.controller.ts b/backend/src/file/file.controller.ts index 551c5b7c..8f7997f6 100644 --- a/backend/src/file/file.controller.ts +++ b/backend/src/file/file.controller.ts @@ -28,7 +28,7 @@ export class FileController { @Query() query: any, @Body() body: string, - @Param("shareId") shareId: string + @Param("shareId") shareId: string, ) { const { id, name, chunkIndex, totalChunks } = query; @@ -39,7 +39,7 @@ export class FileController { data, { index: parseInt(chunkIndex), total: parseInt(totalChunks) }, { id, name }, - shareId + shareId, ); } @@ -47,7 +47,7 @@ export class FileController { @UseGuards(FileSecurityGuard) async getZip( @Res({ passthrough: true }) res: Response, - @Param("shareId") shareId: string + @Param("shareId") shareId: string, ) { const zip = this.fileService.getZip(shareId); res.set({ @@ -64,7 +64,7 @@ export class FileController { @Res({ passthrough: true }) res: Response, @Param("shareId") shareId: string, @Param("fileId") fileId: string, - @Query("download") download = "true" + @Query("download") download = "true", ) { const file = await this.fileService.get(shareId, fileId); diff --git a/backend/src/file/file.service.ts b/backend/src/file/file.service.ts index 51d02398..8c6d77d2 100644 --- a/backend/src/file/file.service.ts +++ b/backend/src/file/file.service.ts @@ -18,14 +18,14 @@ export class FileService { constructor( private prisma: PrismaService, private jwtService: JwtService, - private config: ConfigService + private config: ConfigService, ) {} async create( data: string, chunk: { index: number; total: number }, file: { id?: string; name: string }, - shareId: string + shareId: string, ) { if (!file.id) file.id = crypto.randomUUID(); @@ -40,7 +40,7 @@ export class FileService { let diskFileSize: number; try { diskFileSize = fs.statSync( - `${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk` + `${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`, ).size; } catch { diskFileSize = 0; @@ -62,7 +62,7 @@ export class FileService { // Check if share size limit is exceeded const fileSizeSum = share.files.reduce( (n, { size }) => n + parseInt(size), - 0 + 0, ); const shareSizeSum = fileSizeSum + diskFileSize + buffer.byteLength; @@ -74,23 +74,23 @@ export class FileService { ) { throw new HttpException( "Max share size exceeded", - HttpStatus.PAYLOAD_TOO_LARGE + HttpStatus.PAYLOAD_TOO_LARGE, ); } fs.appendFileSync( `${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`, - buffer + buffer, ); const isLastChunk = chunk.index == chunk.total - 1; if (isLastChunk) { fs.renameSync( `${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`, - `${SHARE_DIRECTORY}/${shareId}/${file.id}` + `${SHARE_DIRECTORY}/${shareId}/${file.id}`, ); const fileSize = fs.statSync( - `${SHARE_DIRECTORY}/${shareId}/${file.id}` + `${SHARE_DIRECTORY}/${shareId}/${file.id}`, ).size; await this.prisma.file.create({ data: { diff --git a/backend/src/file/guard/fileSecurity.guard.ts b/backend/src/file/guard/fileSecurity.guard.ts index 02a5165a..16b1db2d 100644 --- a/backend/src/file/guard/fileSecurity.guard.ts +++ b/backend/src/file/guard/fileSecurity.guard.ts @@ -14,7 +14,7 @@ import { ShareService } from "src/share/share.service"; export class FileSecurityGuard extends ShareSecurityGuard { constructor( private _shareService: ShareService, - private _prisma: PrismaService + private _prisma: PrismaService, ) { super(_shareService, _prisma); } @@ -24,7 +24,7 @@ export class FileSecurityGuard extends ShareSecurityGuard { const shareId = Object.prototype.hasOwnProperty.call( request.params, - "shareId" + "shareId", ) ? request.params.shareId : request.params.id; @@ -52,7 +52,7 @@ export class FileSecurityGuard extends ShareSecurityGuard { if (share.security?.maxViews && share.security.maxViews <= share.views) { throw new ForbiddenException( "Maximum views exceeded", - "share_max_views_exceeded" + "share_max_views_exceeded", ); } diff --git a/backend/src/jobs/jobs.service.ts b/backend/src/jobs/jobs.service.ts index 18efc785..d6dd1ff4 100644 --- a/backend/src/jobs/jobs.service.ts +++ b/backend/src/jobs/jobs.service.ts @@ -14,7 +14,7 @@ export class JobsService { constructor( private prisma: PrismaService, private reverseShareService: ReverseShareService, - private fileService: FileService + private fileService: FileService, ) {} @Cron("0 * * * *") @@ -56,7 +56,7 @@ export class JobsService { if (expiredReverseShares.length > 0) { this.logger.log( - `Deleted ${expiredReverseShares.length} expired reverse shares` + `Deleted ${expiredReverseShares.length} expired reverse shares`, ); } } @@ -77,7 +77,7 @@ export class JobsService { for (const file of temporaryFiles) { const stats = fs.statSync( - `${SHARE_DIRECTORY}/${shareDirectory}/${file}` + `${SHARE_DIRECTORY}/${shareDirectory}/${file}`, ); const isOlderThanOneDay = moment(stats.mtime) .add(1, "day") diff --git a/backend/src/reverseShare/dto/reverseShareTokenWithShares.ts b/backend/src/reverseShare/dto/reverseShareTokenWithShares.ts index 1e475013..a496ffb6 100644 --- a/backend/src/reverseShare/dto/reverseShareTokenWithShares.ts +++ b/backend/src/reverseShare/dto/reverseShareTokenWithShares.ts @@ -23,7 +23,7 @@ export class ReverseShareTokenWithShares extends OmitType(ReverseShareDTO, [ return partial.map((part) => plainToClass(ReverseShareTokenWithShares, part, { excludeExtraneousValues: true, - }) + }), ); } } diff --git a/backend/src/reverseShare/reverseShare.controller.ts b/backend/src/reverseShare/reverseShare.controller.ts index 32afcaf4..c0b76667 100644 --- a/backend/src/reverseShare/reverseShare.controller.ts +++ b/backend/src/reverseShare/reverseShare.controller.ts @@ -23,7 +23,7 @@ import { ReverseShareService } from "./reverseShare.service"; export class ReverseShareController { constructor( private reverseShareService: ReverseShareService, - private config: ConfigService + private config: ConfigService, ) {} @Post() @@ -44,7 +44,7 @@ export class ReverseShareController { if (!isValid) throw new NotFoundException("Reverse share token not found"); return new ReverseShareDTO().from( - await this.reverseShareService.getByToken(reverseShareToken) + await this.reverseShareService.getByToken(reverseShareToken), ); } @@ -52,7 +52,7 @@ export class ReverseShareController { @UseGuards(JwtGuard) async getAllByUser(@GetUser() user: User) { return new ReverseShareTokenWithShares().fromList( - await this.reverseShareService.getAllByUser(user.id) + await this.reverseShareService.getAllByUser(user.id), ); } diff --git a/backend/src/reverseShare/reverseShare.service.ts b/backend/src/reverseShare/reverseShare.service.ts index 156f6ed0..29eeec54 100644 --- a/backend/src/reverseShare/reverseShare.service.ts +++ b/backend/src/reverseShare/reverseShare.service.ts @@ -10,7 +10,7 @@ export class ReverseShareService { constructor( private config: ConfigService, private prisma: PrismaService, - private fileService: FileService + private fileService: FileService, ) {} async create(data: CreateReverseShareDTO, creatorId: string) { @@ -19,8 +19,8 @@ export class ReverseShareService { .add( data.shareExpiration.split("-")[0], data.shareExpiration.split( - "-" - )[1] as moment.unitOfTime.DurationConstructor + "-", + )[1] as moment.unitOfTime.DurationConstructor, ) .toDate(); @@ -28,7 +28,7 @@ export class ReverseShareService { if (globalMaxShareSize < data.maxShareSize) throw new BadRequestException( - `Max share size can't be greater than ${globalMaxShareSize} bytes.` + `Max share size can't be greater than ${globalMaxShareSize} bytes.`, ); const reverseShare = await this.prisma.reverseShare.create({ diff --git a/backend/src/share/dto/myShare.dto.ts b/backend/src/share/dto/myShare.dto.ts index 7b5449e9..e548da54 100644 --- a/backend/src/share/dto/myShare.dto.ts +++ b/backend/src/share/dto/myShare.dto.ts @@ -27,7 +27,7 @@ export class MyShareDTO extends OmitType(ShareDTO, [ fromList(partial: Partial[]) { return partial.map((part) => - plainToClass(MyShareDTO, part, { excludeExtraneousValues: true }) + plainToClass(MyShareDTO, part, { excludeExtraneousValues: true }), ); } } diff --git a/backend/src/share/dto/share.dto.ts b/backend/src/share/dto/share.dto.ts index 03d4821b..317a3019 100644 --- a/backend/src/share/dto/share.dto.ts +++ b/backend/src/share/dto/share.dto.ts @@ -29,7 +29,7 @@ export class ShareDTO { fromList(partial: Partial[]) { return partial.map((part) => - plainToClass(ShareDTO, part, { excludeExtraneousValues: true }) + plainToClass(ShareDTO, part, { excludeExtraneousValues: true }), ); } } diff --git a/backend/src/share/guard/createShare.guard.ts b/backend/src/share/guard/createShare.guard.ts index 2e2442ad..f6c694b9 100644 --- a/backend/src/share/guard/createShare.guard.ts +++ b/backend/src/share/guard/createShare.guard.ts @@ -7,7 +7,7 @@ import { ReverseShareService } from "src/reverseShare/reverseShare.service"; export class CreateShareGuard extends JwtGuard { constructor( configService: ConfigService, - private reverseShareService: ReverseShareService + private reverseShareService: ReverseShareService, ) { super(configService); } @@ -21,7 +21,7 @@ export class CreateShareGuard extends JwtGuard { if (!reverseShareTokenId) return false; const isReverseShareTokenValid = await this.reverseShareService.isValid( - reverseShareTokenId + reverseShareTokenId, ); return isReverseShareTokenValid; diff --git a/backend/src/share/guard/shareOwner.guard.ts b/backend/src/share/guard/shareOwner.guard.ts index 5d0913ea..7be0be9a 100644 --- a/backend/src/share/guard/shareOwner.guard.ts +++ b/backend/src/share/guard/shareOwner.guard.ts @@ -16,7 +16,7 @@ export class ShareOwnerGuard implements CanActivate { const request: Request = context.switchToHttp().getRequest(); const shareId = Object.prototype.hasOwnProperty.call( request.params, - "shareId" + "shareId", ) ? request.params.shareId : request.params.id; diff --git a/backend/src/share/guard/shareSecurity.guard.ts b/backend/src/share/guard/shareSecurity.guard.ts index 8bbab54f..8b43c8c1 100644 --- a/backend/src/share/guard/shareSecurity.guard.ts +++ b/backend/src/share/guard/shareSecurity.guard.ts @@ -14,7 +14,7 @@ import { ShareService } from "src/share/share.service"; export class ShareSecurityGuard implements CanActivate { constructor( private shareService: ShareService, - private prisma: PrismaService + private prisma: PrismaService, ) {} async canActivate(context: ExecutionContext) { @@ -22,7 +22,7 @@ export class ShareSecurityGuard implements CanActivate { const shareId = Object.prototype.hasOwnProperty.call( request.params, - "shareId" + "shareId", ) ? request.params.shareId : request.params.id; @@ -44,13 +44,13 @@ export class ShareSecurityGuard implements CanActivate { if (share.security?.password && !shareToken) throw new ForbiddenException( "This share is password protected", - "share_password_required" + "share_password_required", ); if (!(await this.shareService.verifyShareToken(shareId, shareToken))) throw new ForbiddenException( "Share token required", - "share_token_required" + "share_token_required", ); return true; diff --git a/backend/src/share/guard/shareTokenSecurity.guard.ts b/backend/src/share/guard/shareTokenSecurity.guard.ts index 0b332417..fbc098f4 100644 --- a/backend/src/share/guard/shareTokenSecurity.guard.ts +++ b/backend/src/share/guard/shareTokenSecurity.guard.ts @@ -16,7 +16,7 @@ export class ShareTokenSecurity implements CanActivate { const request: Request = context.switchToHttp().getRequest(); const shareId = Object.prototype.hasOwnProperty.call( request.params, - "shareId" + "shareId", ) ? request.params.shareId : request.params.id; diff --git a/backend/src/share/share.controller.ts b/backend/src/share/share.controller.ts index b6534129..7bd46ad1 100644 --- a/backend/src/share/share.controller.ts +++ b/backend/src/share/share.controller.ts @@ -33,7 +33,7 @@ export class ShareController { @UseGuards(JwtGuard) async getMyShares(@GetUser() user: User) { return new MyShareDTO().fromList( - await this.shareService.getSharesByUser(user.id) + await this.shareService.getSharesByUser(user.id), ); } @@ -54,11 +54,11 @@ export class ShareController { async create( @Body() body: CreateShareDTO, @Req() request: Request, - @GetUser() user: User + @GetUser() user: User, ) { const { reverse_share_token } = request.cookies; return new ShareDTO().from( - await this.shareService.create(body, user, reverse_share_token) + await this.shareService.create(body, user, reverse_share_token), ); } @@ -74,7 +74,7 @@ export class ShareController { async complete(@Param("id") id: string, @Req() request: Request) { const { reverse_share_token } = request.cookies; return new ShareDTO().from( - await this.shareService.complete(id, reverse_share_token) + await this.shareService.complete(id, reverse_share_token), ); } @@ -91,7 +91,7 @@ export class ShareController { async getShareToken( @Param("id") id: string, @Res({ passthrough: true }) response: Response, - @Body() body: SharePasswordDto + @Body() body: SharePasswordDto, ) { const token = await this.shareService.getShareToken(id, body.password); response.cookie(`share_${id}_token`, token, { diff --git a/backend/src/share/share.service.ts b/backend/src/share/share.service.ts index 4ab83507..6276d4ec 100644 --- a/backend/src/share/share.service.ts +++ b/backend/src/share/share.service.ts @@ -28,7 +28,7 @@ export class ShareService { private config: ConfigService, private jwtService: JwtService, private reverseShareService: ReverseShareService, - private clamScanService: ClamScanService + private clamScanService: ClamScanService, ) {} async create(share: CreateShareDTO, user?: User, reverseShareToken?: string) { @@ -46,7 +46,7 @@ export class ShareService { // If share is created by a reverse share token override the expiration date const reverseShare = await this.reverseShareService.getByToken( - reverseShareToken + reverseShareToken, ); if (reverseShare) { expirationDate = reverseShare.shareExpiration; @@ -57,8 +57,8 @@ export class ShareService { .add( share.expiration.split("-")[0], share.expiration.split( - "-" - )[1] as moment.unitOfTime.DurationConstructor + "-", + )[1] as moment.unitOfTime.DurationConstructor, ) .toDate(); } else { @@ -134,13 +134,13 @@ export class ShareService { if (share.files.length == 0) throw new BadRequestException( - "You need at least on file in your share to complete it." + "You need at least on file in your share to complete it.", ); // Asynchronously create a zip of all files if (share.files.length > 1) this.createZip(id).then(() => - this.prisma.share.update({ where: { id }, data: { isZipReady: true } }) + this.prisma.share.update({ where: { id }, data: { isZipReady: true } }), ); // Send email for each recipient @@ -150,7 +150,7 @@ export class ShareService { share.id, share.creator, share.description, - share.expiration + share.expiration, ); } @@ -161,7 +161,7 @@ export class ShareService { ) { await this.emailService.sendMailToReverseShareCreator( share.reverseShare.creator.email, - share.id + share.id, ); } @@ -285,7 +285,7 @@ export class ShareService { if (share.security?.maxViews && share.security.maxViews <= share.views) { throw new ForbiddenException( "Maximum views exceeded", - "share_max_views_exceeded" + "share_max_views_exceeded", ); } @@ -305,7 +305,7 @@ export class ShareService { { expiresIn: moment(expiration).diff(new Date(), "seconds") + "s", secret: this.config.get("internal.jwtSecret"), - } + }, ); } diff --git a/backend/src/user/dto/updateOwnUser.dto.ts b/backend/src/user/dto/updateOwnUser.dto.ts index 6b7abec8..8d9f73ea 100644 --- a/backend/src/user/dto/updateOwnUser.dto.ts +++ b/backend/src/user/dto/updateOwnUser.dto.ts @@ -2,5 +2,5 @@ import { OmitType, PartialType } from "@nestjs/swagger"; import { UserDTO } from "./user.dto"; export class UpdateOwnUserDTO extends PartialType( - OmitType(UserDTO, ["isAdmin", "password"] as const) + OmitType(UserDTO, ["isAdmin", "password"] as const), ) {} diff --git a/backend/src/user/dto/user.dto.ts b/backend/src/user/dto/user.dto.ts index 7f37fa86..5207f552 100644 --- a/backend/src/user/dto/user.dto.ts +++ b/backend/src/user/dto/user.dto.ts @@ -31,7 +31,7 @@ export class UserDTO { fromList(partial: Partial[]) { return partial.map((part) => - plainToClass(UserDTO, part, { excludeExtraneousValues: true }) + plainToClass(UserDTO, part, { excludeExtraneousValues: true }), ); } } diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts index 5088b45b..1256120f 100644 --- a/backend/src/user/user.controller.ts +++ b/backend/src/user/user.controller.ts @@ -35,7 +35,7 @@ export class UserController { @UseGuards(JwtGuard) async updateCurrentUser( @GetUser() user: User, - @Body() data: UpdateOwnUserDTO + @Body() data: UpdateOwnUserDTO, ) { return new UserDTO().from(await this.userService.update(user.id, data)); } @@ -44,7 +44,7 @@ export class UserController { @UseGuards(JwtGuard) async deleteCurrentUser( @GetUser() user: User, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, ) { response.cookie("access_token", "accessToken", { maxAge: -1 }); response.cookie("refresh_token", "", { diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index cb8fa266..7b900e36 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -11,7 +11,7 @@ import { UpdateUserDto } from "./dto/updateUser.dto"; export class UserSevice { constructor( private prisma: PrismaService, - private emailService: EmailService + private emailService: EmailService, ) {} async list() { @@ -46,7 +46,7 @@ export class UserSevice { if (e.code == "P2002") { const duplicatedField: string = e.meta.target[0]; throw new BadRequestException( - `A user with this ${duplicatedField} already exists` + `A user with this ${duplicatedField} already exists`, ); } } @@ -66,7 +66,7 @@ export class UserSevice { if (e.code == "P2002") { const duplicatedField: string = e.meta.target[0]; throw new BadRequestException( - `A user with this ${duplicatedField} already exists` + `A user with this ${duplicatedField} already exists`, ); } } diff --git a/frontend/src/components/share/FilePreview.tsx b/frontend/src/components/share/FilePreview.tsx index f6c45ff8..07d2889d 100644 --- a/frontend/src/components/share/FilePreview.tsx +++ b/frontend/src/components/share/FilePreview.tsx @@ -44,6 +44,7 @@ const FilePreview = ({ href={`/api/shares/${shareId}/files/${fileId}?download=false`} > View original file + {/* Add translation? */} ); diff --git a/frontend/src/components/upload/modals/showCreateUploadModal.tsx b/frontend/src/components/upload/modals/showCreateUploadModal.tsx index 9944503f..7f6abf37 100644 --- a/frontend/src/components/upload/modals/showCreateUploadModal.tsx +++ b/frontend/src/components/upload/modals/showCreateUploadModal.tsx @@ -140,7 +140,7 @@ const CreateUploadModalBody = ({ diff --git a/frontend/src/i18n/locales.ts b/frontend/src/i18n/locales.ts index 4ca470f7..9bdd268f 100644 --- a/frontend/src/i18n/locales.ts +++ b/frontend/src/i18n/locales.ts @@ -7,6 +7,7 @@ import french from "./translations/fr-FR"; import portuguese from "./translations/pt-BR"; import russian from "./translations/ru-RU"; import chineseSimplified from "./translations/zh-CN"; +import thai from "./translations/th-TH"; export const LOCALES = { ENGLISH: { @@ -54,4 +55,9 @@ export const LOCALES = { code: "ru-RU", messages: russian, }, + THAI: { + name: "ไทย", + code: "th-TH", + messages: thai, + }, }; diff --git a/frontend/src/i18n/translations/th-TH.ts b/frontend/src/i18n/translations/th-TH.ts new file mode 100644 index 00000000..fa5df496 --- /dev/null +++ b/frontend/src/i18n/translations/th-TH.ts @@ -0,0 +1,438 @@ +export default { + // Navbar + "navbar.upload": "อัพโหลด", + "navbar.signin": "เข้าสู่ระบบ", + "navbar.home": "หน้าหลัก", + "navbar.signup": "สมัครบัญชี", + + "navbar.links.shares": "แชร์ของฉัน", + "navbar.links.reverse": "รีเวิร์สแชร์", + + "navbar.avatar.account": "บัญชีของฉัน", + "navbar.avatar.admin": "แผงควบคุมระบบ", + "navbar.avatar.signout": "ออกจากระบบ", + // END navbar + + // / + "home.title": "แพลตฟอร์มสำหรับแชร์ไฟล์ที่คุณสามารถโฮสต์ด้วยตนเอง.", + + "home.description": + "คุณอยากให้บริษัทภายนอกเช่น WeTransfer เข้าถึงไฟล์ส่วนตัวของคุณหรือเปล่า?", + "home.bullet.a.name": "Self-Hosted", + "home.bullet.a.description": "โฮสต์ Pingvin Share บนเครื่องของคุณเอง.", + "home.bullet.b.name": "ความเป็นส่วนตัว", + "home.bullet.b.description": + "ไฟล์ของคุณคือไฟล์ของคุณ มันไม่ควรตกอยู่บนมือของบุคคลที่สาม", + "home.bullet.c.name": "ไม่มีการจำกัดขนาดไฟล์ที่น่ารำคาญ", + "home.bullet.c.description": + "อัพโหลดใหญ่เท่าไหนก็ได้ จำกัดอย่างเดียวคือความจุของเคื่องคุณ", + + "home.button.start": "เริ่มต้น", + "home.button.source": "ซอร์สโค้ด", + // END / + + // /auth/signin + "signin.title": "ยินดีต้อนรับกลับ", + "signin.description": "ยังไม่มีบัญชี?", + "signin.button.signup": "สมัครบัญชี", + "signin.input.email-or-username": "อีเมล์์์์์์์์หรือชื่อผู้ใช้", + "signin.input.email-or-username.placeholder": + "อีเมล์์์์์์์หรือชื่อผู้ใช้ของคุณ", + "signin.input.password": "รหัสผ่าน", + "signin.input.password.placeholder": "รหัสผ่านของคุณ", + "signin.button.submit": "เข้าสู่ระบบ", + "signIn.notify.totp-required.title": "ยืนยันตรวจสอบสิทธิ์สองปัจจัย", + "signIn.notify.totp-required.description": "กรุณาใส่รหัสยืนยันตัวตนสองปัจจัย", + + // END /auth/signin + + // /auth/signup + "signup.title": "สมัครบัญชี", + "signup.description": "มีบัญชีแล้ว?", + "signup.button.signin": "เข้าสู่ระบบ", + "signup.input.username": "ชื่อผู้ใช้", + "signup.input.username.placeholder": "ชื่อผู้ใช้ของคุณ", + "signup.input.email": "อีเมล์์์์์์์์", + "signup.input.email.placeholder": "อีเมล์์์์์์์์ของคุณ", + "signup.button.submit": "เริ่มต้นกัน", + + // END /auth/signup + + // /auth/reset-password + "resetPassword.title": "ลืมรหัสผ่าน?", + "resetPassword.description": "กรุณาใส่อีเมล์์์์์์์ของคุณเพื่อรีเซ็ตรหัสผ่าน", + "resetPassword.notify.success": + "ส่งอีเมล์์์์์์์พร้อมลิงค์เพื่อรีเซ็ตรหัสผ่านของคุณแล้ว", + "resetPassword.button.back": "กลับไปที่หน้าลงชื่อเข้าใช้", + "resetPassword.text.resetPassword": "รีเซ็ตรหัสผ่าน", + "resetPassword.text.enterNewPassword": "ป้อนรหัสผ่านใหม่ของคุณ", + "resetPassword.input.password": "รหัสผ่านใหม่", + "resetPassword.notify.passwordReset": "รหัสผ่านของคุณรีเซ็ตเรียบร้อยแล้ว", + + // /account + "account.title": "บัญชีของฉัน", + + "account.card.info.title": "ข้อมูลบัญชี", + "account.card.info.username": "ชื่อผู้ใช้", + "account.card.info.email": "อีเมล์์์์์์์", + "account.notify.info.success": "อัปเดตบัญชีเรียบร้อยแล้ว", + + "account.card.password.title": "รหัสผ่าน", + "account.card.password.old": "รหัสผ่านเก่า", + "account.card.password.new": "รหัสผ่านใหม่", + "account.notify.password.success": "อัปเดตรหัสผ่านเรียบร้อยแล้ว", + + "account.card.security.title": "ความปลอดภัย", + "account.card.security.totp.enable.description": + "ใส่รหัสผ่านปัจจุบันของคุณเพื่อเริ่มต้นการเปิดใช้งาน TOTP", + "account.card.security.totp.disable.description": + "ใส่รหัสผ่านปัจจุบันของคุณเพื่อปิดใช้งาน TOTP", + "account.card.security.totp.button.start": "เริ่มต้น", + "account.modal.totp.title": "เปิดใช้งาน TOTP", + "account.modal.totp.step1": "ขั้นตอนที่ 1: สแกนรหัส QR", + "account.modal.totp.step2": "ขั้นตอนที่ 2: ป้อนรหัสยืนยันตัวตน", + "account.modal.totp.enterManually": "ป้อนด้วยตนเอง", + "account.modal.totp.code": "รหัส", + "account.modal.totp.clickToCopy": "คลิกเพื่อคัดลอก", + "account.modal.totp.verify": "ยืนยัน", + "account.notify.totp.disable": "TOTP ถูกปิดใช้งานเรียบร้อยแล้ว", + "account.notify.totp.enable": "TOTP ถูกเปิดใช้งานเรียบร้อยแล้ว", + + "account.card.language.title": "ภาษา", + "account.card.language.description": + "โปรเจคนี้ถูกแปลโดยชุมชน บางภาษาอาจยังไม่สมบูรณ์", + "account.card.color.title": "ธีมสี", + + // ThemeSwitcher.tsx + "account.theme.dark": "มืด", + "account.theme.light": "สว่าง", + "account.theme.system": "ตามระบบ", + + "account.button.delete": "ลบบัญชี", + "account.modal.delete.title": "ลบบัญชีของคุณ", + "account.modal.delete.description": + "คุณต้องการลบบัญชีของคุณหรือไม่ รวมถึงแชร์ที่คุณสร้างไว้ทั้งหมด?", + // END /account + + // /account/shares + "account.shares.title": "แชร์ของฉัน", + "account.shares.title.empty": "มันว่างเปล่าที่นี่ 👀", + "account.shares.description.empty": "คุณยังไม่ได้สร้างแชร์ใดๆ", + "account.shares.button.create": "สร้างแชร์", + + "account.shares.info.title": "ข้อมูลแชร์", + "account.shares.table.id": "ID", + "account.shares.table.name": "ชื่อ", + "account.shares.table.description": "คำอธิบาย", + "account.shares.table.visitors": "ผู้เข้าชม", + "account.shares.table.expiresAt": "หมดอายุ", + "account.shares.table.createdAt": "สร้างเมื่อ", + "account.shares.table.size": "ขนาด", + + "account.shares.modal.share-informations": "ข้อมูลแชร์", + "account.shares.modal.share-link": "แชร์ลิงค์", + + "account.shares.modal.delete.title": "ลบแชร์ {share}", + "account.shares.modal.delete.description": "คุณต้องการลบแชร์นี้หรือไม่?", + + // END /account/shares + + // /account/reverseShares + "account.reverseShares.title": "รีเวิร์สแชร์ของฉัน", + "account.reverseShares.description": + "รีเวิร์สแชร์สร้างลิงค์สำหรับคนภายนอกเพื่อที่จะแชร์ไฟล์ให้คุณ", + + "account.reverseShares.title.empty": "มันว่างเปล่าที่นี่ 👀", + "account.reverseShares.description.empty": "คุณยังไม่ได้สร้างรีเวิร์สแชร์ใดๆ", + + // showCreateReverseShareModal.tsx + "account.reverseShares.modal.expiration.label": "ลิงค์หมดอายุใน", + "account.reverseShares.modal.expiration.minute-singular": "นาที", + "account.reverseShares.modal.expiration.minute-plural": "นาที", + "account.reverseShares.modal.expiration.hour-singular": "ชั่วโมง", + "account.reverseShares.modal.expiration.hour-plural": "ชั่วโมง", + "account.reverseShares.modal.expiration.day-singular": "วัน", + "account.reverseShares.modal.expiration.day-plural": "วัน", + "account.reverseShares.modal.expiration.week-singular": "สัปดาห์", + "account.reverseShares.modal.expiration.week-plural": "สัปดาห์", + "account.reverseShares.modal.expiration.month-singular": "เดือน", + "account.reverseShares.modal.expiration.month-plural": "เดือน", + "account.reverseShares.modal.expiration.year-singular": "ปี", + "account.reverseShares.modal.expiration.year-plural": "ปี", + + "account.reverseShares.modal.max-size.label": "ขนาดสูงสุดของแชร์", + + "account.reverseShares.modal.send-email": "ส่งอีเมล์์์์์์์แจ้งเตือน", + "account.reverseShares.modal.send-email.description": + "ส่งอีเมล์์์์์์์แจ้งเตือนเมื่อมีการสร้างแชร์ด้วยลิงค์รีเวิร์สนี้", + + "account.reverseShares.modal.max-use.label": "จำนวนการใช้งานสูงสุด", + "account.reverseShares.modal.max-use.description": + "จำนวนครั้งสูงสุดที่ลิงค์นี้สามารถใช้งานได้", + "account.reverseShare.never-expires": "ลิงค์นี้ไม่มีวันหมดอายุ", + "account.reverseShare.expires-on": "ลิงค์นี้จะหมดอายุใน {expiration}.", + + "account.reverseShares.table.no-shares": "ยังไม่มีการสร้างแชร์", + "account.reverseShares.table.count.singular": "แชร์", + "account.reverseShares.table.count.plural": "แชร์", + "account.reverseShares.table.shares": "แชร์", + "account.reverseShares.table.remaining": "เหลืออีก {count} ครั้ง", + "account.reverseShares.table.max-size": "ขนาดสูงสุดของแชร์", + "account.reverseShares.table.expires": "หมดอายุ", + + "account.reverseShares.modal.reverse-share-link": "ลิงค์รีเวิร์สแชร์", + + "account.reverseShares.modal.delete.title": "ลบลิงค์รีเวิร์สแชร์", + "account.reverseShares.modal.delete.description": + "คุณต้องการลบลิงค์รีเวิร์สแชร์นี้หรือไม่? หากคุณทำเช่นนั้นแชร์ที่เกี่ยวข้องจะถูกลบด้วย", + + // END /account/reverseShares + + // /admin + "admin.title": "แผงควบคุมระบบ", + "admin.button.users": "การจัดการผู้ใช้", + "admin.button.config": "การตั้งค่า", + "admin.version": "เวอร์ชัน", + // END /admin + + // /admin/users + "admin.users.title": "การจัดการผู้ใช้", + "admin.users.table.username": "ชื่อผู้ใช้", + "admin.users.table.email": "อีเมล์์์์์์์", + "admin.users.table.admin": "ผู้ดูแลระบบ", + + "admin.users.edit.update.title": "อัปเดตผู้ใช้ {username}", + "admin.users.edit.update.admin-privileges": "สิทธิ์ของผู้ดูแลระบบ", + "admin.users.edit.update.change-password.title": "เปลี่ยนรหัสผ่าน", + "admin.users.edit.update.change-password.field": "รหัสผ่านใหม่", + "admin.users.edit.update.change-password.button": "บันทึกรหัสผ่านใหม่", + "admin.users.edit.update.notify.password.success": + "รหัสผ่านเปลี่ยนเรียบร้อยแล้ว", + + "admin.users.edit.delete.title": "ลบผู้ใช้ {username}", + "admin.users.edit.delete.description": + "คุณต้องการลบผู้ใช้นี้และการแชร์ทั้งหมดของเขาหรือไม่?", + + // showCreateUserModal.tsx + "admin.users.modal.create.title": "สร้างผู้ใช้", + "admin.users.modal.create.username": "ชื่อผู้ใช้", + "admin.users.modal.create.email": "อีเมล์", + "admin.users.modal.create.password": "รหัสผ่าน", + "admin.users.modal.create.manual-password": "ตั้งรหัสผ่านด้วยตนเอง", + "admin.users.modal.create.manual-password.description": + "หากไม่ติ๊กเลือก ผู้ใช้จะได้รับอีเมล์พร้อมลิงก์เพื่อตั้งรหัสผ่านด้วยตนเอง", + "admin.users.modal.create.admin": "สิทธิ์ของผู้ดูแลระบบ", + "admin.users.modal.create.admin.description": + "หากติ๊กเลือก ผู้ใช้จะสามารถเข้าถึงแผงควบคุมระบบได้", + + // END /admin/users + + // /upload + "upload.title": "อัปโหลด", + + "upload.notify.generic-error": "เกิดข้อผิดพลาดขณะที่กำลังจัดการการแชร์ของคุณ", + "upload.notify.count-failed": + "มีไฟล์จำนวน {count} ไฟล์ที่ไม่สามารถอัปโหลดได้ กำลังลองอีกครั้ง", + + // Dropzone.tsx + "upload.dropzone.title": "อัปโหลดไฟล์", + "upload.dropzone.description": + "ลากและวางไฟล์ที่นี่เพื่อเริ่มการแชร์ของคุณ สามารถรับไฟล์ที่มีขนาดรวมไม่เกิน {maxSize} เท่านั้น", + "upload.dropzone.notify.file-too-big": + "ไฟล์ของคุณเกินขนาดสูงสุดของการแชร์ {maxSize}", + + // FileList.tsx + "upload.filelist.name": "ชื่อ", + "upload.filelist.size": "ขนาด", + + // showCreateUploadModal.tsx + "upload.modal.title": "สร้างการแชร์", + "upload.modal.link.error.invalid": + "สามารถใช้ได้เฉพาะตัวอักษร ตัวเลข ขีดล่าง และขีดเส้น", + "upload.modal.link.error.taken": "ลิงก์นี้ถูกใช้งานแล้ว", + "upload.modal.not-signed-in": "คุณยังไม่ได้เข้าสู่ระบบ", + "upload.modal.not-signed-in-description": + "คุณจะไม่สามารถลบการแชร์ของคุณด้วยตนเองและดูจำนวนผู้เข้าชมได้", + + "upload.modal.expires.never": "ไม่มีกำหนด", + "upload.modal.expires.never-long": "ไม่มีกำหนดหมดอายุ", + + "upload.modal.link.label": "ลิงค์", + "upload.modal.expires.label": "หมดอายุ", + "upload.modal.expires.minute-singular": "นาที", + "upload.modal.expires.minute-plural": "นาที", + "upload.modal.expires.hour-singular": "ชั่วโมง", + "upload.modal.expires.hour-plural": "ชั่วโมง", + "upload.modal.expires.day-singular": "วัน", + "upload.modal.expires.day-plural": "วัน", + "upload.modal.expires.week-singular": "สัปดาห์", + "upload.modal.expires.week-plural": "สัปดาห์", + "upload.modal.expires.month-singular": "เดือน", + "upload.modal.expires.month-plural": "เดือน", + "upload.modal.expires.year-singular": "ปี", + "upload.modal.expires.year-plural": "ปี", + + "upload.modal.accordion.description.title": "คำอธิบาย", + "upload.modal.accordion.description.placeholder": + "หมายเหตุสำหรับผู้รับการแชร์นี้", + + "upload.modal.accordion.email.title": "ผู้รับอีเมล์", + "upload.modal.accordion.email.placeholder": "ป้อนผู้รับอีเมล์", + "upload.modal.accordion.email.invalid-email": "ที่อยู่อีเมล์ไม่ถูกต้อง", + + "upload.modal.accordion.security.title": "ตัวเลือกความปลอดภัย", + "upload.modal.accordion.security.password.label": "การป้องกันรหัสผ่าน", + "upload.modal.accordion.security.password.placeholder": "ไม่มีรหัสผ่าน", + "upload.modal.accordion.security.max-views.label": "จำนวนการเข้าชมสูงสุด", + "upload.modal.accordion.security.max-views.placeholder": "ไม่จำกัด", + + // showCompletedUploadModal.tsx + "upload.modal.completed.never-expires": "การแชร์นี้จะไม่มีวันหมดอายุ", + "upload.modal.completed.expires-on": + "การแชร์นี้จะหมดอายุเมื่อวันที่ {expiration}", + "upload.modal.completed.share-ready": "แชร์พร้อมใช้งาน", + + // END /upload + + // /share/[id] + "share.title": "แชร์ {shareId}", + "share.description": "ดูสิ่งที่ฉันแชร์กับคุณ!", + "share.error.visitor-limit-exceeded.title": "เกินขีดจำกัดผู้เข้าชม", + "share.error.visitor-limit-exceeded.description": + "การแชร์นี้ได้เกินขีดจำกัดผู้เข้าชมแล้ว", + "share.error.removed.title": "การแชร์ถูกลบ", + "share.error.not-found.title": "ไม่พบการแชร์นี้", + "share.error.not-found.description": "การแชร์ที่คุณกำลังมองหาไม่มีอยู่จริง", + + "share.modal.password.title": "ต้องการรหัสผ่าน", + "share.modal.password.description": "กรุณาป้อนรหัสผ่านเพื่อเข้าถึงการแชร์นี้", + "share.modal.password": "รหัสผ่าน", + "share.modal.error.invalid-password": "รหัสผ่านไม่ถูกต้อง", + + "share.button.download-all": "ดาวน์โหลดทั้งหมด", + "share.notify.download-all-preparing": + "กำลังเตรียมการแชร์นี้ โปรดลองอีกครั้งในไม่กี่นาที", + + "share.modal.file-link": "ลิงค์ไฟล์", + "share.table.name": "ชื่อ", + "share.table.size": "ขนาด", + + "share.modal.file-preview.error.not-supported.title": + "ไม่รองรับการแสดงตัวอย่าง", + "share.modal.file-preview.error.not-supported.description": + "ไม่รองรับการแสดงตัวอย่างสำหรับไฟล์ประเภทนี้ โปรดดาวน์โหลดไฟล์เพื่อดู", + + // END /share/[id] + + // END /share/[id] + + // /admin/config + "admin.config.title": "การตั้งค่า", + "admin.config.category.general": "ทั่วไป", + "admin.config.category.share": "การแชร์", + "admin.config.category.email": "อีเมล์์์์์์", + "admin.config.category.smtp": "SMTP", + + "admin.config.general.app-name": "ชื่อแอพ", + "admin.config.general.app-name.description": "ชื่อแอพพลิเคชัน", + "admin.config.general.app-url": "URL ของแอพ", + "admin.config.general.app-url.description": + "URL ที่สามารถเข้าถึงแอพพลิเคชัน Pingvin Share ได้", + "admin.config.general.show-home-page": "แสดงหน้าแรก", + "admin.config.general.show-home-page.description": + "หากติ๊ก เว็บไซต์จะแสดงหน้าหลักเวลาเข้าถึง URL หลัก", + "admin.config.general.logo": "โลโก้", + "admin.config.general.logo.description": + "เปลี่ยนโลโก้โดยอัปโหลดรูปภาพใหม่ รูปภาพต้องเป็น PNG และควรมีขนาดอัตราส่วน 1:1", + "admin.config.general.logo.placeholder": "คลิกที่นี่หรือลากไฟล์มา", + + "admin.config.email.enable-share-email-recipients": + "เปิดใช้งานผู้รับอีเมล์์์์์์ของการแชร์", + "admin.config.email.enable-share-email-recipients.description": + "เมื่อเปิดใช้งานผู้รับอีเมล์์์์์์ของการแชร์ ผู้ใช้ที่สร้างการแชร์จะสามารถเพิ่มผู้รับอีเมล์์์์์์์ของการแชร์ได้ เปิดเฉพาะเมื่อคุณเปิดใช้งาน SMTP", + "admin.config.email.share-recipients-subject": + "หัวเรื่องผู้รับอีเมล์์์์์์ของการแชร์", + "admin.config.email.share-recipients-subject.description": + "หัวเรื่องของอีเมล์์์์์์์์ที่ส่งไปยังผู้รับอีเมล์์์์์์ของการแชร์", + "admin.config.email.share-recipients-message": + "ข้อความผู้รับอีเมล์์์์์์ของการแชร์", + "admin.config.email.share-recipients-message.description": + "ข้อความที่ส่งไปยังผู้รับอีเมล์์์์์์ของการแชร์ ตัวแปรที่ใช้ได้:\n {creator} - ชื่อผู้สร้างการแชร์\n {shareUrl} - URL ของการแชร์\n {desc} - คำอธิบายของการแชร์\n {expires} - วันหมดอายุของการแชร์\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง", + "admin.config.email.reverse-share-subject": "หัวเรื่องการแชร์รีเวิร์ส", + "admin.config.email.reverse-share-subject.description": + "หัวเรื่องของอีเมล์์์์์์ที่ส่งไปยังผู้สร้างการแชร์รีเวิร์ส", + "admin.config.email.reverse-share-message": "ข้อความการแชร์รีเวิร์ส", + "admin.config.email.reverse-share-message.description": + "ข้อความที่ส่งไปยังผู้สร้างการแชร์รีเวิร์ส ตัวแปรที่ใช้ได้:\n {shareUrl} - ชื่อผู้สร้างแชร์รีเวิร์สและ URL ของการแชร์\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง", + "admin.config.email.reset-password-subject": "หัวเรื่องการรีเซ็ตรหัสผ่าน", + "admin.config.email.reset-password-subject.description": + "หัวเรื่องของอีเมล์์์์์์ที่ส่งไปยังผู้ใช้เมื่อขอรีเซ็ตรหัสผ่าน", + "admin.config.email.reset-password-message": "ข้อความการรีเซ็ตรหัสผ่าน", + "admin.config.email.reset-password-message.description": + "ข้อความที่ส่งไปยังผู้ใช้เมื่อขอรีเซ็ตรหัสผ่าน ตัวแปรที่ใช้ได้:\n {url} - URL สำหรับรีเซ็ตรหัสผ่าน\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง", + // "Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.", + "admin.config.email.invite-subject": "หัวเรื่องการเชิญ", + "admin.config.email.invite-subject.description": + "หัวเรื่องของอีเมล์์์์์์ที่ส่งไปยังผู้ใช้เมื่อผู้ดูแลระบบเชิญผู้ใช้", + "admin.config.email.invite-message": "ข้อความการเชิญ", + "admin.config.email.invite-message.description": + "ข้อความที่ส่งไปยังผู้ใช้เมื่อผู้ดูแลระบบเชิญผู้ใช้ ตัวแปรที่ใช้ได้:\n {url} - URL สำหรับเชิญผู้ใช้\n {password} - รหัสผ่านของผู้ใช้\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง", + "admin.config.share.allow-registration": "อนุญาตให้ลงทะเบียนด้วยตัวเอง", + "admin.config.share.allow-registration.description": + "อนุญาตให้ผู้ใช้ลงทะเบียนด้วยตัวเองเพื่อสร้างแชร์", + "admin.config.share.allow-unauthenticated-shares": + "อนุญาตให้แชร์โดยไม่ต้องเข้าสู่ระบบ", + "admin.config.share.allow-unauthenticated-shares.description": + "อนุญาตให้ผู้ใช้ที่ไม่ได้เข้าสู่ระบบสร้างแชร์", + "admin.config.share.max-size": "ขนาดสูงสุด", + "admin.config.share.max-size.description": "ขนาดสูงสุดของแชร์", + "admin.config.share.zip-compression-level": "ระดับการบีบอัดไฟล์ Zip", + "admin.config.share.zip-compression-level.description": + "ปรับระดับเพื่อปรับความสมดุลระหว่างขนาดไฟล์และความเร็วในการบีบอัด ค่าอยู่ระหว่าง 0-9 โดย 0 คือไม่มีการบีบอัดและ 9 คือการบีบอัดสูงสุด", + + "admin.config.smtp.enabled": "เปิด", + "admin.config.smtp.enabled.description": + "เปิดใช้งาน SMTP สำหรับการส่งอีเมล์์์์์์ เปิดได้เท่านั้นต่อเมื่อคุณใส่ข้อมูลโฮสต์ พอร์ต อีเมล์ ผู้ใช้ และรหัสผ่านของเซิร์ฟเวอร์ SMTP ของคุณ", + "admin.config.smtp.host": "โฮสต์", + "admin.config.smtp.host.description": "โฮสต์ของเซิร์ฟเวอร์ SMTP", + "admin.config.smtp.port": "พอร์ต", + "admin.config.smtp.port.description": "พอร์ตของเซิร์ฟเวอร์ SMTP", + "admin.config.smtp.email": "อีเมล์์์์์์์", + "admin.config.smtp.email.description": + "อีเมล์์์์์์์ที่ใช้สำหรับการส่งอีเมล์์์์์์์", + "admin.config.smtp.username": "ชื่อผู้ใช้", + "admin.config.smtp.username.description": "ชื่อผู้ใช้ของเซิร์ฟเวอร์ SMTP", + "admin.config.smtp.password": "รหัสผ่าน", + "admin.config.smtp.password.description": "รหัสผ่านของเซิร์ฟเวอร์ SMTP", + "admin.config.smtp.button.test": "ส่งอีเมล์์์์์์ทดสอบ", + + // 404 + "404.description": "ไม่พบหน้าที่คุณกำลังมองหา", + "404.button.home": "หน้าแรก", + + // Common translations + "common.button.save": "บันทึก", + "common.button.create": "สร้าง", + "common.button.submit": "ส่ง", + "common.button.delete": "ลบ", + "common.button.cancel": "ยกเลิก", + "common.button.confirm": "ยืนยัน", + "common.button.disable": "ปิดการใช้งาน", + "common.button.share": "แชร์", + "common.button.generate": "สุ่ม", + "common.button.done": "เสร็จสิ้น", + "common.text.link": "ลิงค์", + "common.text.or": "หรือ", + "common.button.go-back": "ย้อนกลับ", + "common.notify.copied": "คัดลอกไปยังคลิปบอร์ดแล้ว", + "common.success": "สำเร็จ", + + "common.error": "เกิดข้อผิดพลาด", + "common.error.unknown": "เกิดข้อผิดพลาดที่ไม่รู้จัก", + "common.error.invalid-email": "ที่อยู่อีเมล์ไม่ถูกต้อง", + "common.error.too-short": "ต้องมีอย่างน้อย {length} ตัวอักษร", + "common.error.too-long": "ต้องมีไม่เกิน {length} ตัวอักษร", + "common.error.exact-length": "ต้องมีความยาว {length} ตัวอักษร", + "common.error.invalid-number": "ต้องเป็นตัวเลข", + "common.error.field-required": "ต้องกรอกข้อมูลนี้", +};