From f22a6fb322b2cb6dfcfeb7f40c5c73bfeba8c444 Mon Sep 17 00:00:00 2001 From: zyronon Date: Sun, 10 Sep 2023 23:52:59 +0800 Subject: [PATCH] feat(typeartice.vue): add baidu translate --- netlify.toml | 3 + package.json | 7 +- pnpm-lock.yaml | 73 ++++++++++++ src/components/Practice/TypeArticle.vue | 4 +- src/translate/baidu/index.ts | 146 ++++++++++++++++++++++++ src/utils/http.ts | 114 ++++++++++++++++++ vite.config.ts | 4 + 7 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 src/translate/baidu/index.ts create mode 100644 src/utils/http.ts diff --git a/netlify.toml b/netlify.toml index e69de29b..e12870a6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -0,0 +1,3 @@ +[[redirects]] + from = "/baidu" + to = "https://api.fanyi.baidu.com/api/trans/vip/translate" \ No newline at end of file diff --git a/package.json b/package.json index 8adedd61..b833d99f 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,16 @@ "dev": "vite", "build": "vue-tsc && vite build", "build2": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "commit": "git-cz", + "prepare": "husky install", + "test": "" }, "dependencies": { "@icon-park/vue-next": "^1.4.2", "@opentranslate/baidu": "^1.4.2", "@opentranslate/translator": "^1.4.2", + "axios": "^1.5.0", "element-plus": "^2.3.9", "hover.css": "^2.3.2", "localforage": "^1.10.0", @@ -32,6 +36,7 @@ "@vue/compiler-sfc": "^3.3.4", "commitizen": "^4.3.0", "cz-conventional-changelog": "^3.3.0", + "husky": "^8.0.3", "sass": "^1.64.2", "typescript": "^5.2.0", "unplugin-icons": "^0.16.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 118b1c4f..26f7ca65 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@opentranslate/translator': specifier: ^1.4.2 version: 1.4.2 + axios: + specifier: ^1.5.0 + version: 1.5.0 element-plus: specifier: ^2.3.9 version: 2.3.9(vue@3.3.4) @@ -67,6 +70,9 @@ devDependencies: cz-conventional-changelog: specifier: ^3.3.0 version: 3.3.0 + husky: + specifier: ^8.0.3 + version: 8.0.3 sass: specifier: ^1.64.2 version: 1.64.2 @@ -1128,6 +1134,10 @@ packages: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1142,6 +1152,16 @@ packages: - supports-color dev: false + /axios@1.5.0: + resolution: {integrity: sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -1320,6 +1340,13 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commitizen@4.3.0: resolution: {integrity: sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==} engines: {node: '>= 12'} @@ -1471,6 +1498,11 @@ packages: clone: 1.0.4 dev: true + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /detect-file@1.0.0: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} engines: {node: '>=0.10.0'} @@ -1656,6 +1688,16 @@ packages: resolve-dir: 1.0.1 dev: true + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /follow-redirects@1.5.10: resolution: {integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==} engines: {node: '>=4.0'} @@ -1665,6 +1707,15 @@ packages: - supports-color dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /franc-min@4.1.1: resolution: {integrity: sha512-7xpOX5GymdaT6d0qmSNFpyFuEd6tPuEHVZpL+KIh9DocVCVn59c2OnUvLn+NcWGGi7btqV1VZ5VbjSox5KbZKA==} dependencies: @@ -1826,6 +1877,12 @@ packages: engines: {node: '>=10.17.0'} dev: true + /husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true + dev: true + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -2162,6 +2219,18 @@ packages: picomatch: 2.3.1 dev: true + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2387,6 +2456,10 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} diff --git a/src/components/Practice/TypeArticle.vue b/src/components/Practice/TypeArticle.vue index 79e2f7d4..f4aaae29 100644 --- a/src/components/Practice/TypeArticle.vue +++ b/src/components/Practice/TypeArticle.vue @@ -24,6 +24,7 @@ import {useEventListener} from "@/hooks/useEvent.ts"; import TypeWord from "@/components/Practice/TypeWord.vue"; import {emitter, EventKey} from "@/utils/eventBus.ts"; import Baidu from "@opentranslate/baidu"; +import {AxiosInstance} from "@/utils/http"; let article1 = `How does the older investor differ in his approach to investment from the younger investor? There is no shortage of tipsters around offering 'get-rich-quick' opportunities. But if you are a serious private investor, leave the Las Vegas mentality to those with money to fritter. The serious investor needs a proper 'portfolio' -- a well-planned selection of investments, with a definite structure and a clear aim. But exactly how does a newcomer to the stock market go about achieving that? @@ -96,12 +97,13 @@ onMounted(() => { let sections = useSplitArticle(article.article) let temp = useSplitCNArticle(article.articleTranslate, 'cn', CnKeyboardMap) const baidu = new Baidu({ + axios: AxiosInstance, config: { appid: "20230910001811857", key: "Xxe_yftQR3K3Ue43NQMC" } }) - baidu.translate('fuck', 'en','zh-CN').then(r => { + baidu.translate('fuck', 'en', 'zh-CN').then(r => { console.log('s', r) }) practiceStore.total = 0 diff --git a/src/translate/baidu/index.ts b/src/translate/baidu/index.ts new file mode 100644 index 00000000..bfdede46 --- /dev/null +++ b/src/translate/baidu/index.ts @@ -0,0 +1,146 @@ +import { + Language, + Translator, + TranslateError, + TranslateQueryResult +} from "@opentranslate/translator"; +import md5 from "md5"; +import qs from "qs"; + +const langMap: [Language, string][] = [ + ["auto", "auto"], + ["zh-CN", "zh"], + ["en", "en"], + ["yue", "yue"], + ["wyw", "wyw"], + ["ja", "jp"], + ["ko", "kor"], + ["fr", "fra"], + ["es", "spa"], + ["th", "th"], + ["ar", "ara"], + ["ru", "ru"], + ["pt", "pt"], + ["de", "de"], + ["it", "it"], + ["el", "el"], + ["nl", "nl"], + ["pl", "pl"], + ["bg", "bul"], + ["et", "est"], + ["da", "dan"], + ["fi", "fin"], + ["cs", "cs"], + ["ro", "rom"], + ["sl", "slo"], + ["sv", "swe"], + ["hu", "hu"], + ["zh-TW", "cht"], + ["vi", "vie"] +]; + +export interface BaiduConfig { + placeholder?: string; + appid: string; + key: string; +} + +export class BaiduTranslate { + readonly name = "baidu"; + apiUrl = "https://api.fanyi.baidu.com/api/trans/vip/translate"; + + readonly endpoint = "https://api.fanyi.baidu.com/api/trans/vip/translate"; + + protected async query( + text: string, + from: Language, + to: Language, + config: BaiduConfig + ): Promise { + type BaiduTranslateError = { + error_code: "54001" | string; + error_msg: "Invalid Sign" | string; + }; + + type BaiduTranslateResult = { + from: string; + to: string; + trans_result: Array<{ + dst: string; + src: string; + }>; + }; + + const salt = Date.now(); + const {endpoint} = this; + const {appid, key} = config; + + const res = await this.request( + endpoint, + { + params: { + from: Baidu.langMap.get(from), + to: Baidu.langMap.get(to), + q: text, + salt, + appid, + sign: md5(appid + text + salt + key) + } + } + ).catch(() => { + throw new TranslateError("NETWORK_ERROR"); + }); + + const {data} = res; + + if ((data as BaiduTranslateError).error_code) { + console.error( + new Error("[Baidu service]" + (data as BaiduTranslateError).error_msg) + ); + throw new TranslateError("API_SERVER_ERROR"); + } + + const { + trans_result: transResult, + from: langDetected + } = data as BaiduTranslateResult; + const transParagraphs = transResult.map(({dst}) => dst); + const detectedFrom = Baidu.langMapReverse.get(langDetected) as Language; + + return { + text, + from: detectedFrom, + to, + origin: { + paragraphs: transResult.map(({src}) => src), + tts: await this.textToSpeech(text, detectedFrom) + }, + trans: { + paragraphs: transParagraphs, + tts: await this.textToSpeech(transParagraphs.join(" "), to) + } + }; + } + + /** Translator lang to custom lang */ + private static readonly langMap = new Map(langMap); + + /** Custom lang to translator lang */ + private static readonly langMapReverse = new Map( + langMap.map(([translatorLang, lang]) => [lang, translatorLang]) + ); + + getSupportLanguages(): Language[] { + return [...Baidu.langMap.keys()]; + } + + async textToSpeech(text: string, lang: Language): Promise { + return `https://fanyi.baidu.com/gettts?${qs.stringify({ + lan: Baidu.langMap.get(lang !== "auto" ? lang : "zh-CN") || "zh", + text, + spd: 5, + })}`; + } +} + +export default Baidu; diff --git a/src/utils/http.ts b/src/utils/http.ts new file mode 100644 index 00000000..0ad9ad25 --- /dev/null +++ b/src/utils/http.ts @@ -0,0 +1,114 @@ +import axios from 'axios' +// import globalMethods from './global-methods' +// import Config from '../config/index' +// import CONSTANT from './const_var' +// import store from '../store' +// import Storage from './storage' + +export const AxiosInstance = axios.create({ + // baseURL: process.env.NODE_ENV === 'production' ? Config.PRODUCT_API_URL : Config.API_URL, + // baseURL: 'http://testtestgp.com', + timeout: 15000, +}) + +// request 拦截器 +AxiosInstance.interceptors.request.use( + (config) => { + console.log('config', config) + if (config.url === 'https://api.fanyi.baidu.com/api/trans/vip/translate'){ + config.url = '/baidu' + } + return config + }, + error => Promise.reject(error), +) + +// respone 拦截器 +// instance.interceptors.response.use( +// // 响应正常的处理 +// (response) => { +// // console.log(response) +// // console.log(response.data) +// const { data } = response +// if (response.status !== 200) { +// globalMethods.$warning(response.statusText) +// return Promise.reject(data) +// } +// if (data === null) { +// return Promise.resolve({ +// code: '009900', +// msg: '系统出现错误', +// data: {}, +// }) +// } +// return Promise.resolve(data) +// }, +// // 请求出错的处理 +// (error) => { +// console.log(error) +// if (error.response === undefined && error.status === undefined) { +// return Promise.resolve({ +// code: '009900', +// msg: '服务器响应超时', +// data: null, +// }) +// } +// if (error.response.status >= 500) { +// return Promise.resolve({ +// code: '009900', +// msg: '服务器出现错误', +// data: null, +// }) +// } +// if (error.response.status === 401) { +// return Promise.resolve({ +// code: '009900', +// msg: '用户名或密码不正确', +// data: null, +// }) +// } +// const { data } = error.response +// if (data.code !== undefined) { +// return Promise.resolve({ +// code: data.code, +// msg: data.msg, +// }) +// } +// return Promise.resolve({ +// code: '009900', +// msg: data.msg, +// data: null, +// }) +// }, +// ) + + +/** + * @apiDescription 封装的网络请求方法 + * @apiGroup + * @apiName request + * @apiParam url 地址 + * @apiParam data 请求数据 + * @apiParam params 请求参数 + * @apiParam method 方法类型:get或者post + * @apiParam version 接口版本号 + * @apiParamExample + * request('Appointment/appointmentList', data, params, CONSTANT.GET) + * @apiReturn Promise + */ +// async function request(url, data = {}, params = {}, method = CONSTANT.POST, version = Config.API_VERSION) { +// // console.log(url) +// if (method === CONSTANT.POST) { +// data.userId = store.state.user.userInfo === null ? '' : store.state.user.userInfo.id +// } else { +// params.userId = store.state.user.userInfo === null ? '' : store.state.user.userInfo.id +// } +// return instance({ +// url: version + url, +// method, +// data, +// params, +// }) +// } + +// export default request \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 72f19258..77400f76 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -33,6 +33,10 @@ export default defineConfig({ host: '0.0.0.0', fs: { strict: false, + }, + proxy: { + '/baidu': 'https://api.fanyi.baidu.com/api/trans/vip/translate' } + } })