Commit 1324bcb1 authored by mingyard's avatar mingyard

feat:翻译狗相关接口

parent 133fdb1d
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node-terminal",
"name": "运行脚本: start",
"request": "launch",
"command": "yarn run start",
"cwd": "${workspaceFolder}"
}
]
}
\ No newline at end of file
...@@ -101,3 +101,71 @@ export enum SupportLanguages { ...@@ -101,3 +101,71 @@ export enum SupportLanguages {
UK = 'UK', UK = 'UK',
ZH = 'ZH', ZH = 'ZH',
} }
// 翻译狗支持的语言
export const FAN_YI_GOU_SOURCE_LANGUAGES = [
{ key: 'pl', language: 'Polish', name: '波兰' },
{ key: 'dan', language: 'Danish', name: '丹麦语' },
{ key: 'de', language: 'German', name: '德语' },
{ key: 'ru', language: 'Russian', name: '俄语' },
{ key: 'fra', language: 'French', name: '法语' },
{ key: 'kor', language: 'Korean', name: '韩语' },
{ key: 'nl', language: 'Dutch', name: '荷兰语' },
{ key: 'rom', language: 'Romanian', name: '罗马尼亚语' },
{ key: 'ms', language: 'Malay', name: '马来语' },
{ key: 'pt', language: 'Portuguese', name: '葡萄牙语' },
{ key: 'jp', language: 'Japanese', name: '日语' },
{ key: 'swe', language: 'Swedish', name: '瑞典语' },
{ key: 'tr', language: 'Turkish', name: '土耳其语' },
{ key: 'spa', language: 'Spanish', name: '西班牙语' },
{ key: 'el', language: 'Greek', name: '希腊语' },
{ key: 'hu', language: 'Hungarian', name: '匈牙利语' },
{ key: 'it', language: 'Italian', name: '意大利语' },
{ key: 'id', language: 'Indonesian', name: '印尼语' },
{ key: 'en', language: 'English', name: '英语' },
{ key: 'zh', language: 'Chinese', name: '中文' },
];
// 翻译狗支持的目标语言
export const FAN_YI_GOU_TARGET_LANGUAGES = [
{ key: 'kor', language: 'Korean', name: '韩语' },
{ key: 'jp', language: 'Japanese', name: '日语' },
{ key: 'th', language: 'Thai', name: '泰语' },
{ key: 'el', language: 'Greek', name: '希腊语' },
{ key: 'en', language: 'English', name: '英语' },
{ key: 'zh', language: 'Chinese', name: '中文' },
];
// 翻译狗支持的语言
export const FanYiGouSourceLanguages = {
PL: 'pl',
DAN: 'dan',
DE: 'de',
RU: 'ru',
FRA: 'fra',
KOR: 'kor',
NL: 'nl',
ROM: 'rom',
MS: 'ms',
PT: 'pt',
JP: 'jp',
SWE: 'swe',
TR: 'tr',
SPA: 'spa',
EL: 'el',
HU: 'hu',
IT: 'it',
ID: 'id',
EN: 'en',
ZH: 'zh',
};
// 翻译狗支持的目标语言
export const FanYiGouTargetLanguages = {
KOR: 'kor',
JP: 'jp',
TH: 'th',
EL: 'el',
EN: 'en',
ZH: 'zh',
};
...@@ -7,6 +7,7 @@ export interface ServiceConfig { ...@@ -7,6 +7,7 @@ export interface ServiceConfig {
fanYiGou: { fanYiGou: {
endpoint: string; endpoint: string;
secret: string; secret: string;
appId: string;
}; };
} }
...@@ -17,5 +18,6 @@ export const service: ServiceConfig = { ...@@ -17,5 +18,6 @@ export const service: ServiceConfig = {
fanYiGou: { fanYiGou: {
endpoint: env.FAN_YI_GOU_ENDPOINT ?? 'https://www.fanyigou.com', endpoint: env.FAN_YI_GOU_ENDPOINT ?? 'https://www.fanyigou.com',
secret: env.FAN_YI_GOU_SECRET ?? '', secret: env.FAN_YI_GOU_SECRET ?? '',
appId: env.FAN_YI_GOU_APP_ID ?? '',
}, },
}; };
import {
FanYiGouSourceLanguages,
FanYiGouTargetLanguages,
} from '@/common/utils/constants';
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { IsEnum, IsNotEmpty } from 'class-validator';
export class TranslateImageReqDto {
@ApiProperty({
description: '目标语言',
enum: FanYiGouTargetLanguages,
example: FanYiGouTargetLanguages.EN,
})
@Expose()
@IsNotEmpty()
@IsEnum(FanYiGouTargetLanguages)
target: string;
@ApiProperty({
description: '源语言',
enum: FanYiGouSourceLanguages,
example: FanYiGouSourceLanguages.ZH,
})
@Expose()
@IsNotEmpty()
@IsEnum(FanYiGouSourceLanguages)
source: string;
}
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { IsNotEmpty, IsString } from 'class-validator';
export class TranslateProgressReqDto {
@ApiProperty({
description: '任务ID',
type: String,
example: 123456,
})
@Expose()
@IsNotEmpty()
@IsString()
taskId: string;
}
import { Body, Controller, Get, Post } from '@nestjs/common'; import {
Body,
Controller,
Get,
Post,
Query,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { TranslateService } from './translate.service'; import { TranslateService } from './translate.service';
import { Auth } from '@/common/decorators/auth.decorator'; import { Auth } from '@/common/decorators/auth.decorator';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { TranslateReqDto } from './dto/req/translateReq.dto'; import { TranslateReqDto } from './dto/req/translateReq.dto';
import { TranslateResDto } from './dto/res/translateRes.dto'; import { TranslateResDto } from './dto/res/translateRes.dto';
import { FileInterceptor } from '@nestjs/platform-express';
import { TranslateImageReqDto } from './dto/req/translateImageReq.dto';
import { TranslateProgressReqDto } from './dto/req/translateProgressReq.dto';
import { ApiResponseInterceptor } from '@/common/interceptor/api.response.interceptor';
@ApiTags('translate') @ApiTags('translate')
@UseInterceptors(ApiResponseInterceptor)
@Controller('translate') @Controller('translate')
export class TranslateController { export class TranslateController {
constructor(private readonly translateService: TranslateService) {} constructor(private readonly translateService: TranslateService) {}
...@@ -17,8 +30,12 @@ export class TranslateController { ...@@ -17,8 +30,12 @@ export class TranslateController {
status: 200, status: 200,
description: '成功返回支持的语言列表', description: '成功返回支持的语言列表',
example: { example: {
source: [{ key: 'AR', language: 'Arabic', name: '阿拉伯语' }], code: 200,
target: [{ key: 'AR', language: 'Arabic', name: '阿拉伯语' }], message: 'success',
data: {
source: [{ key: 'AR', language: 'Arabic', name: '阿拉伯语' }],
target: [{ key: 'AR', language: 'Arabic', name: '阿拉伯语' }],
},
}, },
}) })
@Auth() @Auth()
...@@ -33,8 +50,12 @@ export class TranslateController { ...@@ -33,8 +50,12 @@ export class TranslateController {
status: 200, status: 200,
description: '成功返回翻译结果', description: '成功返回翻译结果',
example: { example: {
source: 'EN', code: 200,
text: '要翻译的文本', message: 'success',
data: {
source: 'ZH',
text: 'weekly',
},
}, },
}) })
@Auth() @Auth()
...@@ -43,4 +64,98 @@ export class TranslateController { ...@@ -43,4 +64,98 @@ export class TranslateController {
): Promise<TranslateResDto> { ): Promise<TranslateResDto> {
return await this.translateService.translateText(translateDto); return await this.translateService.translateText(translateDto);
} }
// 翻译图片支持的语言
@Get('image/languages')
@ApiOperation({ summary: '获取翻译图片支持的语言' })
@ApiResponse({
status: 200,
description: '成功返回支持的语言列表',
example: {
code: 200,
message: 'success',
data: {
source: [{ key: 'en', language: 'English', name: '英语' }],
target: [{ key: 'zh', language: 'Chinese', name: '中文' }],
},
},
})
@Auth()
async getImageLanguages(): Promise<{
source: object[];
target: object[];
}> {
return await this.translateService.getFanYiGouLanguages();
}
// 翻译图片
@Post('image')
@ApiOperation({ summary: '翻译图片' })
@ApiResponse({
status: 200,
description: '成功返回翻译结果',
example: {
code: 200,
message: 'success',
data: {
taskId: 709394,
},
},
})
@Auth()
@UseInterceptors(
FileInterceptor('file', {
limits: { fileSize: 4 * 1024 * 1024 },
}),
)
async translateImage(
@UploadedFile() file,
@Body() dto: TranslateImageReqDto,
): Promise<any> {
return await this.translateService.translateImage(file, dto);
}
// 获取翻译进度
@Get('progress')
@ApiOperation({ summary: '获取翻译进度' })
@ApiResponse({
status: 200,
description: '成功返回翻译进度',
example: {
code: 200,
message: 'success',
data: {
tid: 709412,
createTime: '2025-01-15 16:54:08',
updateTime: '',
status: 1,
title: '20250111-165032',
pageCount: 1,
percent: '0.00',
fromLan: '英语',
detectLan: '',
toLan: '中文',
docType: 'jpeg',
msg: '进行中',
originalCharCount: 0,
},
},
})
@Auth()
async getProgress(@Query() dto: TranslateProgressReqDto): Promise<any> {
return await this.translateService.getProgress(dto.taskId);
}
// 获取翻译结果
@Get('result')
@ApiOperation({ summary: '获取翻译结果' })
@ApiResponse({
status: 200,
description: '成功返回翻译结果',
example: {},
})
@Auth()
async getResult(@Query() dto: TranslateProgressReqDto): Promise<any> {
return await this.translateService.downloadImage(dto.taskId);
}
} }
...@@ -8,7 +8,9 @@ import { TranslateResDto } from './dto/res/translateRes.dto'; ...@@ -8,7 +8,9 @@ import { TranslateResDto } from './dto/res/translateRes.dto';
import { axiosPostRequest } from '@/common/utils/requests/request'; import { axiosPostRequest } from '@/common/utils/requests/request';
import { config } from '@/config'; import { config } from '@/config';
import { BadRequestError } from '@/common/exception/badRequest/BadRequestError'; import { BadRequestError } from '@/common/exception/badRequest/BadRequestError';
import crypto from 'crypto'; import * as crypto from 'crypto';
import * as FormData from 'form-data';
import { TranslateImageReqDto } from './dto/req/translateImageReq.dto';
@Injectable() @Injectable()
export class TranslateService { export class TranslateService {
...@@ -22,6 +24,17 @@ export class TranslateService { ...@@ -22,6 +24,17 @@ export class TranslateService {
}; };
} }
// 获取翻译狗支持的语言
async getFanYiGouLanguages(): Promise<{
source: object[];
target: object[];
}> {
return {
source: DEEPL_SOURCE_LANGUAGES,
target: DEEPL_TARGET_LANGUAGES,
};
}
// 翻译文本 // 翻译文本
async translateText(translateDto: TranslateReqDto): Promise<TranslateResDto> { async translateText(translateDto: TranslateReqDto): Promise<TranslateResDto> {
console.log('config.deepl', config.deepl); console.log('config.deepl', config.deepl);
...@@ -52,21 +65,137 @@ export class TranslateService { ...@@ -52,21 +65,137 @@ export class TranslateService {
} }
// 获取翻译狗token // 获取翻译狗token
async generateToken(params, privateKey) { generateToken(params) {
params = {
...params,
appid: config.service.fanYiGou.appId,
privatekey: config.service.fanYiGou.secret,
};
// 按照key=value的格式,并按照参数名ASCII字典序排序 // 按照key=value的格式,并按照参数名ASCII字典序排序
const sortedParams = Object.keys(params) const sortedParams = Object.keys(params)
.sort() .sort()
.map((key) => `${key}=${params[key]}`) .map((key) => `${key}=${params[key]}`)
.join('&'); .join('&');
const stringToHash = `${sortedParams}&privatekey=${privateKey}`;
// 生成MD5哈希并转换为大写 // 生成MD5哈希并转换为大写
const token = crypto const token = crypto
.createHash('md5') .createHash('md5')
.update(stringToHash) .update(sortedParams)
.digest('hex') .digest('hex')
.toUpperCase(); .toUpperCase();
return token; return token;
} }
getFileMD5(fileBuffer: Buffer): string {
return crypto.createHash('md5').update(fileBuffer).digest('hex');
}
async translateImage(file, dto: TranslateImageReqDto): Promise<any> {
const params = {
nonce_str: crypto.randomUUID(),
from: dto.source,
to: dto.target,
md5: this.getFileMD5(file.buffer),
};
const token = this.generateToken(params);
// 构建 FormData 数据
const formData = new FormData();
formData.append('file', file.buffer, {
filename: file.originalname,
contentType: file.mimetype,
});
formData.append('appid', config.service.fanYiGou.appId);
formData.append('md5', params.md5);
formData.append('nonce_str', params.nonce_str);
formData.append('from', params.from);
formData.append('to', params.to);
formData.append('token', token);
const result = await axiosPostRequest(
`${config.service.fanYiGou.endpoint}/TranslateApi/api/image/uploadTranslateImage`,
formData,
{
headers: {
...formData.getHeaders(),
},
},
);
if (!result?.data) {
throw BadRequestError.default('翻译失败');
}
return result?.data?.data;
}
// 获取翻译进度
async getProgress(taskId: string): Promise<any> {
const params = {
nonce_str: crypto.randomUUID(),
tid: taskId,
};
const token = this.generateToken(params);
// 构建 FormData 数据
const formData = new FormData();
formData.append('appid', config.service.fanYiGou.appId);
formData.append('nonce_str', params.nonce_str);
formData.append('tid', params.tid);
formData.append('token', token);
const result = await axiosPostRequest(
`${config.service.fanYiGou.endpoint}/TranslateApi/api/image/queryImageTransProgress`,
formData,
{
headers: {
...formData.getHeaders(),
},
},
);
if (!result?.data) {
throw BadRequestError.default('获取翻译进度失败');
}
return result?.data.data;
}
// 下载翻译图片
async downloadImage(taskId: string): Promise<any> {
const params = {
nonce_str: crypto.randomUUID(),
tid: taskId,
};
const token = this.generateToken(params);
// 构建 FormData 数据
const formData = new FormData();
formData.append('appid', config.service.fanYiGou.appId);
formData.append('nonce_str', params.nonce_str);
formData.append('tid', params.tid);
formData.append('token', token);
const result = await axiosPostRequest(
`${config.service.fanYiGou.endpoint}/TranslateApi/api/image/downloadImage`,
formData,
{
headers: {
...formData.getHeaders(),
},
},
);
if (!result?.data) {
throw BadRequestError.default('下载翻译图片失败');
}
return result?.data;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment