├── .commitlintrc.json ├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appcast.json ├── assets └── translate-screenshot.png ├── package.json ├── rollup.config.js ├── scripts ├── bundle.js └── update-appcast.js ├── src ├── api.ts ├── config.ts ├── main.ts ├── types.ts └── utils.ts ├── tsconfig.eslint.json ├── tsconfig.json ├── types └── global.d.ts └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-angular"], 3 | "rules": { 4 | "subject-case": [ 5 | 2, 6 | "always", 7 | ["sentence-case", "start-case", "pascal-case", "upper-case", "lower-case", "camel-case"] 8 | ], 9 | "type-enum": [ 10 | 2, 11 | "always", 12 | [ 13 | "build", 14 | "chore", 15 | "ci", 16 | "docs", 17 | "feat", 18 | "fix", 19 | "perf", 20 | "refactor", 21 | "revert", 22 | "style", 23 | "test", 24 | "sample" 25 | ] 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | release 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { es6: true, node: true }, 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | ecmaVersion: 2020, 7 | sourceType: 'module', 8 | project: './tsconfig.eslint.json', 9 | }, 10 | plugins: ['@typescript-eslint', 'prettier'], 11 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 12 | globals: { 13 | $log: false, 14 | $info: false, 15 | $option: false, 16 | $http: false, 17 | $file: false, 18 | $data: false, 19 | }, 20 | rules: { 21 | '@typescript-eslint/ban-ts-comment': 'off', 22 | '@typescript-eslint/no-extra-semi': 'off', 23 | '@typescript-eslint/no-var-requires': 'off', 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: geekdada 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest] 14 | node-version: [12] 15 | steps: 16 | - name: Fetch repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Cache node modules 25 | uses: actions/cache@v2 26 | with: 27 | path: node_modules 28 | key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }} 29 | restore-keys: | 30 | ${{ runner.OS }}-build-${{ env.cache-name }}- 31 | ${{ runner.OS }}-build- 32 | ${{ runner.OS }}- 33 | 34 | - name: yarn install, build, bundle 35 | run: | 36 | yarn install 37 | yarn make-release 38 | 39 | - name: Commit files 40 | run: | 41 | git config --global user.name 'Roy Li' 42 | git config --global user.email 'me@dada.li' 43 | git commit -am "chore: update appcast.json" 44 | 45 | - name: Push changes 46 | uses: ad-m/github-push-action@master 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} 49 | 50 | - uses: ncipollo/release-action@v1 51 | with: 52 | artifacts: 'release/*.bobplugin' 53 | token: ${{ secrets.GITHUB_TOKEN }} 54 | draft: true 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .idea 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | /release 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | .vercel 29 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | jsxBracketSameLine: true, 6 | }; 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.8.1](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.8.0...v0.8.1) (2023-02-25) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * DeepL API authorization ([711851e](https://github.com/geekdada/bob-plugin-deepl-translate/commit/711851efb41d1c4e24c9b43f2a137919b0f3e368)) 7 | 8 | 9 | 10 | # [0.8.0](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.7.0...v0.8.0) (2021-05-08) 11 | 12 | 13 | ### Features 14 | 15 | * add support for deepl free api token ([b1b65d5](https://github.com/geekdada/bob-plugin-deepl-translate/commit/b1b65d58eb7ef7e853abc79aa6a4982d0bb07663)) 16 | 17 | 18 | 19 | # [0.7.0](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.6.0...v0.7.0) (2021-02-14) 20 | 21 | 22 | ### Features 23 | 24 | * remove support for a translator ([08eaca1](https://github.com/geekdada/bob-plugin-deepl-translate/commit/08eaca1c2a6152a4e321e859b05c36d16e2f9211)) 25 | 26 | 27 | 28 | # [0.6.0](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.5.0...v0.6.0) (2020-12-25) 29 | 30 | 31 | ### Features 32 | 33 | * support new api endpoint ([f1b363a](https://github.com/geekdada/bob-plugin-deepl-translate/commit/f1b363a1207aacaf0e9642ecc6ba0b9577f4a4c0)) 34 | 35 | 36 | 37 | # [0.5.0](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.4.2...v0.5.0) (2020-12-04) 38 | 39 | 40 | ### Features 41 | 42 | * rename api ([655526b](https://github.com/geekdada/bob-plugin-deepl-translate/commit/655526b410e6c3690b595633a868d149776cfc26)) 43 | 44 | 45 | 46 | ## [0.4.2](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.4.1...v0.4.2) (2020-12-03) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * no formality for certain languages ([fde6417](https://github.com/geekdada/bob-plugin-deepl-translate/commit/fde6417c38424d8b55adae2be01187db78829095)) 52 | 53 | 54 | 55 | ## [0.4.1](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.4.0...v0.4.1) (2020-11-26) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * remove glue code ([37c61d5](https://github.com/geekdada/bob-plugin-deepl-translate/commit/37c61d58db210958a67318a49dfe69c6075a6eb9)) 61 | 62 | 63 | 64 | # [0.4.0](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.3.2...v0.4.0) (2020-11-21) 65 | 66 | 67 | ### Features 68 | 69 | * change api endpoint ([f721f01](https://github.com/geekdada/bob-plugin-deepl-translate/commit/f721f0146d1a66e9528ac72704351c470d9dd691)) 70 | 71 | 72 | 73 | ## [0.3.2](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.3.1...v0.3.2) (2020-11-19) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * api domain ([d7fa477](https://github.com/geekdada/bob-plugin-deepl-translate/commit/d7fa477099b6e0133b30652dfefd666ab4ebbdcc)) 79 | 80 | 81 | 82 | ## [0.3.1](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.3.0...v0.3.1) (2020-11-19) 83 | 84 | 85 | 86 | # [0.3.0](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.2.1...v0.3.0) (2020-11-19) 87 | 88 | 89 | ### Features 90 | 91 | * support sub-deepl ([f65cf49](https://github.com/geekdada/bob-plugin-deepl-translate/commit/f65cf49dc4a50cef076a93f2f007ddf7ecc74fcb)) 92 | 93 | 94 | 95 | ## [0.2.1](https://github.com/geekdada/bob-plugin-deepl-translate/compare/v0.2.0...v0.2.1) (2020-11-17) 96 | 97 | 98 | 99 | # 0.2.0 (2020-11-17) 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Roy Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bob-plugin-deepl-translate 2 | 3 | 这是一个 [Bob](https://ripperhe.gitee.io/bob/#/) 使用的 DeepL 翻译插件。 4 | 5 | 你需要有一个 DeepL 的 API Token 才可以使用本插件。 6 | 7 |

translate screenshot

8 | 9 | ## 安装 10 | 11 | 1. 安装 Bob (version >= 0.5.0) 12 | 2. 下载插件 [releases](https://github.com/geekdada/bob-plugin-deepl-translate/releases) 13 | 3. [安装插件](https://ripperhe.gitee.io/bob/#/general/quickstart/plugin?id=%e5%ae%89%e8%a3%85%e6%8f%92%e4%bb%b6) 14 | 15 | ## 配置 16 | 17 | - `token`:DeepL API Token(需自己购买)。 18 | - `formality` 正式程度: 设置翻译后的文本是倾向于正式语言还是非正式语言。此功能目前适用于所有目标语言,除了英语、英式英语、美式英语、西班牙语、汉语和日语。 19 | 20 | ## 注意 21 | 22 | ### 字符是如何计费的? 23 | 24 | > DeepL API 计划对你的翻译量没有限制,并按字符数计算你的使用量。 计算的字符是源文本中的字符,也就是你要翻译的原文。 25 | > 26 | > 一个字符对应于一个 unicode 代码点,其中一个字符可以由多个字节组成。 例如,字符 é 在 UTF-8 中被表示为两个字节。 然而,DeepL 只将其视为一个字符。 隐形字符,如空格、制表符、换行符等也会被计算在内。 27 | -------------------------------------------------------------------------------- /appcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "dev.royli.bob-plugin-deepl-translate", 3 | "versions": [ 4 | { 5 | "version": "0.8.1", 6 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/tag/v0.8.1", 7 | "sha256": "73cfece4b953ddadbad00bc33e5000d24e8acfd2f4b73fd9b3216da7eef44a94", 8 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.8.1/bob-plugin-deepl-translate.bobplugin", 9 | "minBobVersion": "0.5.0" 10 | }, 11 | { 12 | "version": "0.8.0", 13 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/tag/v0.8.0", 14 | "sha256": "7d14cb1f447c80aa843e58f49916f442031355eac80d6fc118864d7266f9390d", 15 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.8.0/bob-plugin-deepl-translate.bobplugin", 16 | "minBobVersion": "0.5.0" 17 | }, 18 | { 19 | "version": "0.7.0", 20 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/tag/v0.7.0", 21 | "sha256": "2f7acffeb598f32ff498392eb2eb72c666ba4b0c61861d88428091748b297fdb", 22 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.7.0/bob-plugin-deepl-translate.bobplugin", 23 | "minBobVersion": "0.5.0" 24 | }, 25 | { 26 | "version": "0.6.0", 27 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/tag/v0.6.0", 28 | "sha256": "ae0f1c9fff74668b289278df4b9569f1e95a2a1f9ccd366aa89b6db3643c1e59", 29 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.6.0/bob-plugin-deepl-translate.bobplugin", 30 | "minBobVersion": "0.5.0" 31 | }, 32 | { 33 | "version": "0.5.0", 34 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 35 | "sha256": "1293d1e3f492f6ea62ff7fa6f85ce6e26d9a19de277b364d33839b97ce669f00", 36 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.5.0/bob-plugin-deepl-translate.bobplugin", 37 | "minBobVersion": "0.5.0" 38 | }, 39 | { 40 | "version": "0.4.2", 41 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 42 | "sha256": "ed7b8a0758a4dfe4660a24509b10323eb456c84b10a8624fd31fefdfb123cae5", 43 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.4.2/bob-plugin-deepl-translate.bobplugin", 44 | "minBobVersion": "0.5.0" 45 | }, 46 | { 47 | "version": "0.4.1", 48 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 49 | "sha256": "39d44479fb6805c4a4175e7933e0aa772d2559cac970a3f3972bcb410c47ccef", 50 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.4.1/bob-plugin-deepl-translate-v0.4.1.bobplugin", 51 | "minBobVersion": "0.5.0" 52 | }, 53 | { 54 | "version": "0.4.0", 55 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 56 | "sha256": "002c725f0541fae988ad52767480ec37aae8a27db2428d1bd1f2f0b060bf5c41", 57 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.4.0/bob-plugin-deepl-translate-v0.4.0.bobplugin", 58 | "minBobVersion": "0.5.0" 59 | }, 60 | { 61 | "version": "0.3.2", 62 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 63 | "sha256": "02d4477f88055426d43b7078ed6519ebc4bad56f28e55fc45956dd6c36890f03", 64 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.3.2/bob-plugin-deepl-translate-v0.3.2.bobplugin", 65 | "minBobVersion": "0.5.0" 66 | }, 67 | { 68 | "version": "0.3.1", 69 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 70 | "sha256": "43fbf1400aa02746ea847680c143a9e4eb797c779eb0d4aa2014acdf6cd662cb", 71 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.3.1/bob-plugin-deepl-translate-v0.3.1.bobplugin", 72 | "minBobVersion": "0.5.0" 73 | }, 74 | { 75 | "version": "0.2.1", 76 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 77 | "sha256": "cf49feef70efc0e11e8371c69793b028b2e16c9f31f9103fd2f53b83972aecae", 78 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.2.1/bob-plugin-deepl-translate-v0.2.1.bobplugin", 79 | "minBobVersion": "0.5.0" 80 | }, 81 | { 82 | "version": "0.2.0", 83 | "desc": "https://github.com/geekdada/bob-plugin-deepl-translate/blob/master/CHANGELOG.md", 84 | "sha256": "ee987385061471c5ca0e684d934c7dee7ac6d0354cecc755c59d7bca166fe7a1", 85 | "url": "https://github.com/geekdada/bob-plugin-deepl-translate/releases/download/v0.2.0/bob-plugin-deepl-translate-v0.2.0.bobplugin", 86 | "minBobVersion": "0.5.0" 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /assets/translate-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geekdada/bob-plugin-deepl-translate/8321dea1ef4758e705f29c8372075d1d77fc55a0/assets/translate-screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bob-plugin-deepl-translate", 3 | "version": "0.8.1", 4 | "author": "Roy Li ", 5 | "homepage": "https://github.com/geekdada/bob-plugin-deepl-translate", 6 | "repository": "https://github.com/geekdada/bob-plugin-deepl-translate.git", 7 | "license": "MIT", 8 | "private": true, 9 | "scripts": { 10 | "clean": "rimraf build && rimraf release", 11 | "build": "run-s clean && cross-env NODE_ENV=production rollup -c rollup.config.js", 12 | "bundle": "cross-env NODE_ENV=production node scripts/bundle.js", 13 | "bundle:watch": "cross-env NODE_ENV=development nodemon --watch build scripts/bundle.js", 14 | "make-release": "run-s build bundle update-appcast", 15 | "dev": "run-s clean && cross-env NODE_ENV=development rollup -c rollup.config.js --watch", 16 | "type-check": "tsc --noEmit", 17 | "update-appcast": "node scripts/update-appcast.js", 18 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", 19 | "pub": "np --no-publish --no-release-draft", 20 | "version": "npm run changelog && git add ." 21 | }, 22 | "dependencies": { 23 | "@commitlint/cli": "^11.0.0", 24 | "@commitlint/config-angular": "^11.0.0", 25 | "@rollup/plugin-commonjs": "^16.0.0", 26 | "@rollup/plugin-json": "^4.1.0", 27 | "@rollup/plugin-node-resolve": "^10.0.0", 28 | "@rollup/plugin-replace": "^2.3.4", 29 | "@rollup/plugin-typescript": "^6.1.0", 30 | "@types/adm-zip": "^0.4.33", 31 | "@types/jsonfile": "^6.0.0", 32 | "@types/node": "^12", 33 | "@typescript-eslint/eslint-plugin": "^4.8.0", 34 | "@typescript-eslint/parser": "^4.8.0", 35 | "adm-zip": "^0.4.16", 36 | "conventional-changelog-cli": "^2.1.1", 37 | "cross-env": "^7.0.2", 38 | "eslint": "^7.13.0", 39 | "eslint-plugin-prettier": "^3.1.4", 40 | "form-data": "^3.0.0", 41 | "fs-extra": "^9.0.1", 42 | "husky": "^4.3.0", 43 | "lint-staged": "^10.4.0", 44 | "nodemon": "^2.0.6", 45 | "np": "^7.0.0", 46 | "npm-run-all": "^4.1.5", 47 | "prettier": "^2.1.2", 48 | "rollup": "^2.28.2", 49 | "rollup-plugin-copy": "^3.3.0", 50 | "rollup-plugin-node-polyfills": "^0.2.1", 51 | "ts-node": "^9.0.0", 52 | "tslib": "^2.0.3", 53 | "type-fest": "^0.19.0", 54 | "typescript": "^4.0.5" 55 | }, 56 | "nodemonConfig": { 57 | "ignore": [ 58 | "build/info.json" 59 | ] 60 | }, 61 | "husky": { 62 | "hooks": { 63 | "pre-commit": "lint-staged", 64 | "commit-msg": "commitlint -c .commitlintrc.json -E HUSKY_GIT_PARAMS" 65 | } 66 | }, 67 | "lint-staged": { 68 | "*.js": "eslint", 69 | "*.ts": "eslint --ext .ts" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import copy from 'rollup-plugin-copy' 3 | import json from '@rollup/plugin-json' 4 | import commonjs from '@rollup/plugin-commonjs' 5 | import resolve from '@rollup/plugin-node-resolve' 6 | import nodePolyfills from 'rollup-plugin-node-polyfills' 7 | import typescript from '@rollup/plugin-typescript' 8 | import replace from '@rollup/plugin-replace' 9 | 10 | export default { 11 | input: join(__dirname, './src/main.ts'), 12 | output: { 13 | format: 'cjs', 14 | exports: 'auto', 15 | dir: join(__dirname, `./build`), 16 | preserveModules: true, 17 | }, 18 | plugins: [ 19 | copy({ 20 | targets: [ 21 | // { src: './src/info.json', dest: `build` }, 22 | ], 23 | }), 24 | json({ namedExports: false }), 25 | resolve({ 26 | extensions: ['.js', '.ts'], 27 | preferBuiltins: true, 28 | }), 29 | commonjs({ 30 | include: ['node_modules/**'], 31 | }), 32 | replace({ 33 | values: { 34 | 'process.env.NODE_ENV': JSON.stringify( 35 | process.env.NODE_ENV || 'development', 36 | ), 37 | 'process.env.__VERSION__': JSON.stringify( 38 | require('./package.json').version, 39 | ), 40 | }, 41 | preventAssignment: true, 42 | }), 43 | nodePolyfills(), 44 | typescript({}), 45 | ], 46 | external: [ 47 | 'crypto-js', 48 | '$util', 49 | '$http', 50 | '$info', 51 | '$option', 52 | '$log', 53 | '$data', 54 | '$file', 55 | ], 56 | } 57 | -------------------------------------------------------------------------------- /scripts/bundle.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | const AdmZip = require('adm-zip') 3 | const fs = require('fs-extra') 4 | const { promisify } = require('util') 5 | 6 | function generateInfo() { 7 | return { 8 | identifier: 'dev.royli.bob-plugin-deepl-translate', 9 | category: 'translate', 10 | name: 'DeepL Translate', 11 | summary: '', 12 | icon: '115', 13 | author: 'Roy Li', 14 | homepage: 'https://github.com/geekdada/bob-plugin-deepl-translate.git', 15 | appcast: 16 | 'https://github.com/geekdada/bob-plugin-deepl-translate/raw/master/appcast.json', 17 | minBobVersion: '0.5.0', 18 | options: [ 19 | { 20 | identifier: 'token', 21 | type: 'text', 22 | title: 'Token', 23 | }, 24 | { 25 | identifier: 'api', 26 | type: 'menu', 27 | title: 'API', 28 | defaultValue: 'deepl-pro', 29 | menuValues: [ 30 | { 31 | title: 'Pro', 32 | value: 'deepl-pro', 33 | }, 34 | { 35 | title: 'Free', 36 | value: 'deepl-free', 37 | }, 38 | ], 39 | }, 40 | { 41 | identifier: 'formality', 42 | type: 'menu', 43 | title: 'Formality', 44 | defaultValue: 'default', 45 | menuValues: [ 46 | { 47 | title: 'Default', 48 | value: 'default', 49 | }, 50 | { 51 | title: 'More formal', 52 | value: 'more', 53 | }, 54 | { 55 | title: 'More informal', 56 | value: 'less', 57 | }, 58 | ], 59 | }, 60 | ], 61 | } 62 | } 63 | 64 | async function main() { 65 | const pkgName = 'bob-plugin-deepl-translate' 66 | const version = require('../package.json').version 67 | const buildDir = join(__dirname, '../build') 68 | const releaseDir = join(__dirname, '../release') 69 | const pkg = join(releaseDir, `${pkgName}.bobplugin`) 70 | const info = { 71 | ...generateInfo(), 72 | version, 73 | } 74 | 75 | await fs.writeJson(join(buildDir, 'info.json'), info) 76 | 77 | const zip = new AdmZip() 78 | zip.addLocalFolder(buildDir) 79 | await promisify(zip.writeZip)(pkg) 80 | } 81 | 82 | main().catch((err) => { 83 | console.error(err) 84 | process.exit(1) 85 | }) 86 | -------------------------------------------------------------------------------- /scripts/update-appcast.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-extra') 3 | const crypto = require('crypto') 4 | 5 | const pkg = require('../package.json') 6 | const plugAppcast = require('../appcast.json') 7 | const githubRelease = `https://github.com/geekdada/bob-plugin-deepl-translate/releases/download` 8 | 9 | function main() { 10 | const pkgName = 'bob-plugin-deepl-translate' 11 | const pkgPath = path.join(__dirname, `../release/${pkgName}.bobplugin`) 12 | const appcastPath = path.join(__dirname, '../appcast.json') 13 | 14 | const fileBuffer = fs.readFileSync(pkgPath) 15 | const sum = crypto.createHash('sha256') 16 | sum.update(fileBuffer) 17 | const hex = sum.digest('hex') 18 | 19 | const version = { 20 | version: pkg.version, 21 | desc: 22 | 'https://github.com/geekdada/bob-plugin-deepl-translate/releases/tag/v' + 23 | pkg.version, 24 | sha256: hex, 25 | url: `${githubRelease}/v${pkg.version}/${pkgName}.bobplugin`, 26 | minBobVersion: '0.5.0', 27 | } 28 | 29 | let versions = (plugAppcast && plugAppcast.versions) || [] 30 | 31 | if (!Array.isArray(versions)) versions = [] 32 | 33 | const index = versions.findIndex((v) => v.version === pkg.version) 34 | 35 | if (index === -1) { 36 | versions.splice(0, 0, version) 37 | } else { 38 | versions.splice(index, 1, version) 39 | } 40 | 41 | const appcastData = { 42 | identifier: 'dev.royli.bob-plugin-deepl-translate', 43 | versions, 44 | } 45 | 46 | fs.outputJSONSync(appcastPath, appcastData, { spaces: 2 }) 47 | } 48 | 49 | main() 50 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RequestCallbackResponse, 3 | RequestObject, 4 | validProvider, 5 | } from '../types/global' 6 | 7 | export class Api { 8 | constructor(private provider: validProvider | null, private token: string) {} 9 | 10 | private get baseUrl(): string { 11 | switch (this.provider) { 12 | case 'deepl-pro': 13 | return 'https://api.deepl.com' 14 | 15 | default: 16 | return 'https://api-free.deepl.com' 17 | } 18 | } 19 | 20 | async request>( 21 | requestObject: Omit, 22 | ): Promise> { 23 | try { 24 | const body: Record = { 25 | ...requestObject.body, 26 | } 27 | const url = `${this.baseUrl}${requestObject.url}` 28 | 29 | return await $http.request({ 30 | ...requestObject, 31 | url, 32 | header: { 33 | 'Content-Type': 'application/x-www-form-urlencoded', 34 | Authorization: `DeepL-Auth-Key ${this.token}`, 35 | }, 36 | body, 37 | }) 38 | } catch (e) { 39 | Object.assign(e, { 40 | _type: 'network', 41 | _message: '接口请求错误 ' + e.message, 42 | }) 43 | 44 | throw e 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | // supported languages 2 | export const supportedLanguages: ReadonlyArray<[string, string]> = [ 3 | ['auto', 'AUTO'], 4 | ['zh-Hans', 'ZH'], 5 | ['en', 'EN'], 6 | ['de', 'DE'], // German 7 | ['fr', 'FR'], // French 8 | ['it', 'IT'], // Italian 9 | ['ja', 'JA'], // Japanese 10 | ['es', 'ES'], // Spanish 11 | ['nl', 'NL'], // Dutch 12 | ['pl', 'PL'], // Polish 13 | ['pt', 'PT'], // Portuguese (all Portuguese varieties mixed) 14 | ['ru', 'RU'], // Russian 15 | ] 16 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Api } from './api' 2 | import { supportedLanguages } from './config' 3 | import { ErrorType, TranslateCompletion, TranslateQuery } from './types' 4 | import { langMap, langMapReverse, translateStatusCode } from './utils' 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 | export function supportLanguages(): ReadonlyArray { 8 | return supportedLanguages.map(([standardLang]) => standardLang) 9 | } 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 12 | export function translate( 13 | query: TranslateQuery, 14 | completion: (data: TranslateCompletion) => void, 15 | ): void { 16 | const api = new Api($option.api, $option.token); 17 | 18 | ; (async () => { 19 | const targetLanguage = langMap.get(query.detectTo) 20 | 21 | $log.info(`translate to ${targetLanguage}`) 22 | 23 | if (!targetLanguage) { 24 | const err = new Error() 25 | Object.assign(err, { 26 | _type: 'unsupportLanguage', 27 | _message: '不支持该语种', 28 | }) 29 | throw err 30 | } 31 | 32 | const formality = isFormalitySupported(targetLanguage) 33 | ? $option.formality 34 | : 'default' 35 | const response = await api.request<{ 36 | translations: ReadonlyArray<{ 37 | detected_source_language: string 38 | text: string 39 | }> 40 | }>({ 41 | method: 'POST', 42 | url: '/v2/translate', 43 | body: { 44 | text: query.text, 45 | target_lang: targetLanguage, 46 | split_sentences: '1', 47 | preserve_formatting: '0', 48 | formality, 49 | }, 50 | }) 51 | 52 | if (response.error) { 53 | const { statusCode } = response.response 54 | 55 | let reason: ErrorType 56 | 57 | if (statusCode >= 400 && statusCode < 500) { 58 | reason = 'param' 59 | } else { 60 | reason = 'api' 61 | } 62 | 63 | completion({ 64 | error: { 65 | type: reason, 66 | message: `接口响应错误 ${translateStatusCode(statusCode)}`, 67 | addtion: JSON.stringify(response), 68 | }, 69 | }) 70 | } else { 71 | const translations = response.data?.translations 72 | 73 | if (!translations || !translations.length) { 74 | completion({ 75 | error: { 76 | type: 'api', 77 | message: '接口未返回翻译结果', 78 | }, 79 | }) 80 | 81 | return 82 | } 83 | 84 | completion({ 85 | result: { 86 | from: langMapReverse.get(translations[0].detected_source_language), 87 | toParagraphs: translations.map((item) => item.text), 88 | }, 89 | }) 90 | } 91 | })().catch((err) => { 92 | completion({ 93 | error: { 94 | type: err._type || 'unknown', 95 | message: err._message || '未知错误', 96 | addtion: err._addtion, 97 | }, 98 | }) 99 | }) 100 | } 101 | 102 | function isFormalitySupported(lang: string): boolean { 103 | const unsupported = ['EN', 'EN-GB', 'EN-US', 'ES', 'JA', 'ZH'] 104 | 105 | return !unsupported.includes(lang.toUpperCase()) 106 | } 107 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // https://ripperhe.gitee.io/bob/#/plugin/quickstart/translate 2 | export interface TranslateQuery { 3 | text: string // 需要翻译的文本。 4 | from: string // 用户选中的源语种标准码,可能是 auto 5 | to: string // 用户选中的目标语种标准码,可能是 auto 6 | detectFrom: string // 检测过后的源语种,一定不是 auto,如果插件不具备检测语种的能力,可直接使用该属性 7 | detectTo: string // 检测过后的目标语种,一定不是 auto,如果不想自行推测用户实际需要的目标语种,可直接使用该属性 8 | } 9 | 10 | export interface TranslateCompletion { 11 | result?: { 12 | from?: string // 由翻译接口提供的源语种,可以与查询时的 from 不同。查看 语种列表。 13 | to?: string // 由翻译接口提供的目标语种,可以与查询时的 to 不同。查看 语种列表。 14 | fromParagraphs?: ReadonlyArray // 原文分段拆分过后的 string 数组,可不传。 15 | toParagraphs: ReadonlyArray // 译文分段拆分过后的 string 数组,必传。 16 | toDict?: never // 词典结果,见 to dict object。可不传。 17 | fromTTS?: never // result 原文的语音合成数据,如果没有,可不传。 18 | toTTS?: never // result 译文的语音合成数据,如果没有,可不传。 19 | raw?: any // 如果插件内部调用了某翻译接口,可将接口原始数据传回,方便定位问题,可不传。 20 | } 21 | error?: { 22 | type: ErrorType // 错误类型,可设置为下方错误之一 23 | message: string // 错误描述,用于展示给用户看 24 | addtion?: any // 附加信息,可以是任何可 json 序列化的数据类型,用于 debug 25 | } 26 | } 27 | 28 | export type ErrorType = 29 | | 'unknown' // 未知错误 30 | | 'param' // 参数错误 31 | | 'unsupportLanguage' // 不支持的语种 32 | | 'secretKey' // 缺少秘钥 33 | | 'network' // 网络异常,网络请失败 34 | | 'api' // 服务接口异常 35 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { supportedLanguages } from './config' 2 | 3 | /** 4 | * 往往各大翻译服务商的语种类型都不同, 5 | * 实现插件的过程中很可能需要服务商特有的语种标识符和 Bob 定义的标识符来回转换, 6 | * 建议以下面的方式实现, 7 | * `xxx` 代表服务商特有的语种标识符,请替换为真实的, 8 | * 具体支持的语种数量根据实际情况而定。 9 | * 10 | * Bob 语种标识符转服务商语种标识符(以为 'zh-Hans' 为例): var lang = langMap.get('zh-Hans'); 11 | * 服务商语种标识符转 Bob 语种标识符: var standardLang = langMapReverse.get('xxx'); 12 | */ 13 | 14 | export const langMap = new Map(supportedLanguages) 15 | 16 | export const langMapReverse = new Map( 17 | supportedLanguages.map(([standardLang, lang]) => [lang, standardLang]), 18 | ) 19 | 20 | export const translateStatusCode = (code: number): string => { 21 | switch (code) { 22 | case 400: 23 | return 'Bad request. Please check error message and your parameters.' 24 | case 403: 25 | return 'Authorization failed. Please supply a valid token.' 26 | case 404: 27 | return 'The requested resource could not be found.' 28 | case 413: 29 | return 'The request size exceeds the limit.' 30 | case 429: 31 | return 'Too many requests. Please wait and resend your request.' 32 | case 456: 33 | return 'Quota exceeded. The character limit has been reached.' 34 | case 503: 35 | return 'Resource currently unavailable. Try again later.' 36 | default: 37 | return 'Internal error' 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | "types/**/*.ts", 6 | "./src/**/*.js", 7 | "./.eslintrc.js", 8 | "./rollup.config.js", 9 | "./scripts/**/*.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | // "outDir": "./build", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "module": "esnext", 8 | "declaration": false, 9 | "inlineSourceMap": false, 10 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 11 | "allowSyntheticDefaultImports": true, // https://zhuanlan.zhihu.com/p/29022311 12 | "resolveJsonModule": true, 13 | "experimentalDecorators": true, 14 | 15 | "strict": true /* Enable all strict type-checking options. */, 16 | 17 | /* Strict Type-Checking Options */ 18 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 19 | // "strictNullChecks": true /* Enable strict null checks. */, 20 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 21 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 22 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 23 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 24 | 25 | /* Additional Checks */ 26 | "noUnusedLocals": false /* Report errors on unused locals. */, 27 | "noUnusedParameters": true /* Report errors on unused parameters. */, 28 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 29 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 30 | 31 | /* Debugging Options */ 32 | "traceResolution": false /* Report module resolution log messages. */, 33 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 34 | "listFiles": false /* Print names of files part of the compilation. */, 35 | "pretty": true /* Stylize errors and messages using color and context. */, 36 | 37 | /* Experimental Options */ 38 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 39 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 40 | 41 | "lib": [ 42 | "esnext" 43 | ], 44 | "types": [ 45 | "node" 46 | ], 47 | "typeRoots": [ 48 | "node_modules/@types", 49 | "types" 50 | ] 51 | }, 52 | "include": ["types/**/*.ts", "src/**/*.ts", "test/**/*.ts"], 53 | "exclude": ["node_modules/**"], 54 | "compileOnSave": false, 55 | "skipLibCheck": true 56 | } 57 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | interface $info { 2 | identifier: string 3 | version: string 4 | category: string 5 | name: string 6 | summary: string 7 | icon: string 8 | author: string 9 | homepage: string 10 | appcast: string 11 | minBobVersion: string 12 | } 13 | 14 | interface $log { 15 | info(obj?: string | Record): void 16 | error(obj?: string | Record): void 17 | } 18 | 19 | interface $http { 20 | request(requestObject: RequestObject): Promise> 21 | } 22 | 23 | export interface RequestObject { 24 | method: 'GET' | 'POST' | 'DELETE' | 'HEAD' 25 | url: string 26 | header?: Record 27 | body?: Record 28 | timeout?: number // 请求超时 29 | } 30 | 31 | export interface RequestCallbackResponse> { 32 | data?: T 33 | response: { 34 | url: string // url 35 | MIMEType: string // MIME 类型 36 | expectedContentLength: number // 长度 37 | textEncodingName: string // 编码 38 | suggestedFilename: string // 建议的文件名 39 | statusCode: number // HTTP 状态码 40 | headers: Record // HTTP header 41 | } 42 | error?: { 43 | domain: string // domain 44 | code: number // code 45 | userInfo: any // userInfo 46 | localizedDescription: string // 描述 47 | localizedFailureReason: string // 原因 48 | localizedRecoverySuggestion: string // 建议 49 | } 50 | } 51 | 52 | interface $option { 53 | token: string 54 | formality: 'default' | 'more' | 'less' 55 | api: validProvider 56 | } 57 | 58 | declare global { 59 | const $info: $info 60 | const $log: $log 61 | const $http: $http 62 | const $option: $option 63 | } 64 | 65 | type validProvider = 'deepl-pro' | 'deepl-free' 66 | --------------------------------------------------------------------------------