-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use 'deepl-node' and store api keys in a better way (#46)
- Use official deepl sdk using npm package 'deepl-node' - Store api key in secret store instead of the extension settings
- Loading branch information
1 parent
a55bd76
commit 57556b1
Showing
11 changed files
with
278 additions
and
394 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,11 +29,7 @@ | |
"Other" | ||
], | ||
"activationEvents": [ | ||
"onStartupFinished", | ||
"onCommand:deepl.translate", | ||
"onCommand:deepl.translateTo", | ||
"onCommand:deepl.translateFromTo", | ||
"onCommand:deepl.translateBelow" | ||
"onStartupFinished" | ||
], | ||
"icon": "resources/[email protected]", | ||
"galleryBanner": { | ||
|
@@ -49,6 +45,7 @@ | |
"deepl.apiKey": { | ||
"type": "string", | ||
"default": "", | ||
"deprecationMessage": "For security reasons the api key will no longer be stored in the configuration. Configured api keys will be automatically migrated to the vscode secret store and removed from the config. The config option will be removed in future releases!", | ||
"markdownDescription": "The key is used to authenticate with the DeepL API. [See offical documentation](https://www.deepl.com/docs-api/accessing-the-api/authentication/)" | ||
}, | ||
"deepl.formality": { | ||
|
@@ -271,6 +268,7 @@ | |
"eslint": "^8.56.0", | ||
"glob": "^10.3.10", | ||
"husky": "^8.0.3", | ||
"lint-staged": "^15.2.0", | ||
"ts-loader": "^9.2.2", | ||
"ts-node": "^10.1.0", | ||
"typescript": "^5.3.3", | ||
|
@@ -279,10 +277,7 @@ | |
}, | ||
"dependencies": { | ||
"@vue/reactivity": "^3.4.10", | ||
"axios": "^1.6.0", | ||
"deepl-node": "^1.11.0", | ||
"lint-staged": "^15.2.0", | ||
"xstate": "4.23.0" | ||
"deepl-node": "^1.11.0" | ||
}, | ||
"lint-staged": { | ||
"*.{ts,tsx}": [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export const DEEPL_CONFIGURATION_SECTION = 'deepl'; | ||
|
||
export const CONFIG_API_KEY = `${DEEPL_CONFIGURATION_SECTION}.apiKey`; | ||
export const CONFIG_FORMALITY = `${DEEPL_CONFIGURATION_SECTION}.formality`; | ||
export const CONFIG_IGNORE_TAGS = `${DEEPL_CONFIGURATION_SECTION}.ignoreTags`; | ||
export const CONFIG_TAG_HANDLING = `${DEEPL_CONFIGURATION_SECTION}.tagHandling`; | ||
export const CONFIG_SPLITTING_TAGS = `${DEEPL_CONFIGURATION_SECTION}.splittingTags`; | ||
export const CONFIG_SPLIT_SENTENCES = `${DEEPL_CONFIGURATION_SECTION}.splitSentences`; | ||
export const CONFIG_NON_SPLITTING_TAGS = `${DEEPL_CONFIGURATION_SECTION}.nonSplittingTags`; | ||
export const CONFIG_PRESERVE_FORMATTING = `${DEEPL_CONFIGURATION_SECTION}.preserveFormatting`; | ||
export const CONFIG_GLOSSARY_ID = `${DEEPL_CONFIGURATION_SECTION}.glossaryId`; | ||
export const CONFIG_DEFAULT_TARGET_LANGUAGE = `${DEEPL_CONFIGURATION_SECTION}.defaultTargetLanguage`; | ||
export const CONFIG_DEFAULT_SOURCE_LANGUAGE = `${DEEPL_CONFIGURATION_SECTION}.defaultSourceLanguage`; | ||
export const WORKSPACE_TARGET_LANGUAGE = `${DEEPL_CONFIGURATION_SECTION}.targetLanguage`; | ||
export const WORKSPACE_SOURCE_LANGUAGE = `${DEEPL_CONFIGURATION_SECTION}.sourceLanguage`; | ||
|
||
export const EXTENSION_IDENTIFIER = 'soerenuhrbach.vscode-deepl'; | ||
|
||
export const COMMAND_CONFIGURE = 'deepl.configure'; | ||
export const COMMAND_TRANSLATE = 'deepl.translate'; | ||
export const COMMAND_TRANSLATE_TO = 'deepl.translateTo'; | ||
export const COMMAND_TRANSLATE_FROM_TO = 'deepl.translateFromTo'; | ||
export const COMMAND_TRANSLATE_BELOW = 'deepl.translateBelow'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,64 @@ | ||
import axios from 'axios'; | ||
import * as vscode from 'vscode'; | ||
import { state } from './state'; | ||
import { Language, LanguageType, Translation } from './types'; | ||
import { isFreeAccountAuthKey } from 'deepl-node'; | ||
|
||
/* eslint-disable */ | ||
export enum DeepLErrorCodes { | ||
BAD_REQUEST = 400, | ||
AUTHORIZATION_FAILED = 403, | ||
NOT_FOUND = 404, | ||
REQUEST_SIZE_EXCEEDED = 413, | ||
URL_TOO_LONG = 414, | ||
TOO_MANY_REQUEST_4XX = 429, | ||
CHARACTER_LIMIT_REACHED = 456, | ||
INTERNAL_SERVER_ERROR = 500, | ||
RESOURCE_UNAVAILABLE = 503, | ||
TOO_MANY_REQUEST_5XX = 529, | ||
} | ||
/* eslint-enable */ | ||
|
||
export class DeepLException extends Error { | ||
public readonly code: DeepLErrorCodes; | ||
|
||
constructor(code: DeepLErrorCodes) { | ||
super(); | ||
this.code = code; | ||
} | ||
|
||
public static createFromStatusCodeAndMessage(code: number, message: string) { | ||
const exception = new DeepLException(code); | ||
exception.name = DeepLException.name; | ||
switch (code) { | ||
case DeepLErrorCodes.AUTHORIZATION_FAILED: | ||
exception.message = "Authentication failed. Please check your DeepL API key. You may be using the DeepL Pro API by mistake."; | ||
break; | ||
|
||
case DeepLErrorCodes.REQUEST_SIZE_EXCEEDED: | ||
exception.message = "Please try again with a shorter text"; | ||
break; | ||
|
||
case DeepLErrorCodes.TOO_MANY_REQUEST_4XX: | ||
case DeepLErrorCodes.TOO_MANY_REQUEST_5XX: | ||
exception.message = "You have done too many translations recently. Please try again later."; | ||
break; | ||
|
||
case DeepLErrorCodes.CHARACTER_LIMIT_REACHED: | ||
exception.message = "You have reached the maximum character limit. You can see your usage [here](https://www.deepl.com/pro-account/usage)"; | ||
break; | ||
|
||
default: | ||
exception.message = "Unfortunately, the DeepL API cannot accept any requests at the moment. Please try again later. (" + message + ")"; | ||
break; | ||
import { Language, SourceLanguageCode, TargetLanguageCode, TextResult, Translator } from 'deepl-node'; | ||
import { EXTENSION_IDENTIFIER } from './constants'; | ||
|
||
const cache = { | ||
targetLanguages: [] as readonly Language[], | ||
sourceLanguages: [] as readonly Language[], | ||
}; | ||
|
||
const createTranslator = (apiKey: string) => { | ||
const extension = vscode.extensions.getExtension(EXTENSION_IDENTIFIER)?.packageJSON; | ||
const appName = extension?.name; | ||
const appVersion = extension?.version; | ||
|
||
return new Translator(apiKey, { | ||
appInfo: { | ||
appName: appName, | ||
appVersion: appVersion | ||
}, | ||
sendPlatformInfo: false | ||
}); | ||
}; | ||
|
||
export function translate<T extends string | string[]>(texts: T, sourceLang: SourceLanguageCode | null, targetLang: TargetLanguageCode): Promise<T extends string ? TextResult : TextResult[]> { | ||
const translator = createTranslator(state.apiKey!); | ||
return translator.translateText( | ||
texts, | ||
sourceLang ?? null, | ||
targetLang, | ||
{ | ||
formality: state.formality || undefined, | ||
glossary: state.glossaryId || undefined, | ||
ignoreTags: state.ignoreTags || undefined, | ||
nonSplittingTags: state.nonSplittingTags || undefined, | ||
splittingTags: state.splittingTags || undefined, | ||
preserveFormatting: state.preserveFormatting || undefined, | ||
splitSentences: state.splitSentences || undefined, | ||
tagHandling: state.tagHandling || undefined | ||
} | ||
return exception; | ||
} | ||
); | ||
} | ||
|
||
type ErrorHandler = (e: DeepLException) => void; | ||
|
||
const http = axios.create(); | ||
const errorHandlers: ErrorHandler[] = []; | ||
|
||
http.interceptors.request.use((config) => { | ||
config.baseURL = isFreeAccountAuthKey(state.apiKey!) | ||
? 'https://api-free.deepl.com' | ||
: 'https://api.deepl.com'; | ||
|
||
if (!config.params) { | ||
config.params = {}; | ||
} | ||
|
||
config.headers.Authorization = `DeepL-Auth-Key ${state.apiKey}`; | ||
|
||
if (config.url!.includes('translate')) { | ||
config.params.split_sentences = state.splitSentences; | ||
config.params.preserve_formatting = state.preserveFormatting ? "1" : "0"; | ||
|
||
if (config.params.source_lang) { | ||
config.params.glossary_id = state.glossaryId; | ||
} | ||
|
||
const formalityAllowed: string[] = state.languages.target | ||
.filter(x => x.supports_formality) | ||
.map(x => x.language); | ||
if (config.params.target_lang && formalityAllowed.includes(config.params.target_lang.toUpperCase())) { | ||
config.params.formality = state.formality; | ||
} | ||
|
||
if (state.tagHandling !== 'off') { | ||
config.params.tag_handling = state.tagHandling; | ||
config.params.ignore_tags = state.ignoreTags; | ||
config.params.splitting_tags = state.splittingTags; | ||
config.params.non_splitting_tags = state.nonSplittingTags; | ||
} | ||
export async function getTargetLanguages() { | ||
if (cache.targetLanguages.length > 0) { | ||
return cache.targetLanguages; | ||
} | ||
|
||
return config; | ||
}); | ||
|
||
http.interceptors.response.use( | ||
res => res, | ||
e => { | ||
if (!e.response) { | ||
throw e; | ||
} | ||
const translator = createTranslator(state.apiKey!); | ||
const languages = await translator.getTargetLanguages(); | ||
cache.targetLanguages = languages; | ||
return languages; | ||
} | ||
|
||
const exception = DeepLException.createFromStatusCodeAndMessage(e.response.status, e.response.data.message); | ||
for (const handler of errorHandlers) { | ||
handler(exception); | ||
} | ||
throw exception; | ||
export async function getSourceLanguages() { | ||
if (cache.sourceLanguages.length > 0) { | ||
return cache.sourceLanguages; | ||
} | ||
); | ||
|
||
export async function translate(text: string, targetLanguage: string, sourceLanguage?: string): Promise<Translation[]> { | ||
const response = await http.post('/v2/translate', null, { | ||
/* eslint-disable */ | ||
params: { | ||
text: text, | ||
target_lang: targetLanguage, | ||
source_lang: sourceLanguage | ||
} | ||
/* eslint-enable */ | ||
}); | ||
|
||
return response.data.translations as Translation[]; | ||
} | ||
|
||
export async function languages(type: LanguageType = 'source'): Promise<Language[]> { | ||
const response = await http.get('/v2/languages', { params: { type } }); | ||
return response.data as Language[]; | ||
} | ||
|
||
export const addErrorHandler = (handler: ErrorHandler) => errorHandlers.push(handler); | ||
const translator = createTranslator(state.apiKey!); | ||
const languages = await translator.getSourceLanguages(); | ||
cache.sourceLanguages = languages; | ||
return languages; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,18 @@ | ||
import * as deepl from './deepl'; | ||
import * as vscode from 'vscode'; | ||
import { setup } from './state'; | ||
import { createStatusBarItem } from './status-bar'; | ||
import { configureSettings, translate, translateFromTo, translateTo, translateBelow } from './commands'; | ||
import { COMMAND_CONFIGURE, COMMAND_TRANSLATE, COMMAND_TRANSLATE_BELOW, COMMAND_TRANSLATE_FROM_TO, COMMAND_TRANSLATE_TO } from './constants'; | ||
|
||
export async function activate(context: vscode.ExtensionContext) { | ||
deepl.addErrorHandler(e => vscode.window.showErrorMessage(e.message)); | ||
|
||
await setup(context); | ||
|
||
context.subscriptions.push(createStatusBarItem()); | ||
context.subscriptions.push(vscode.commands.registerCommand('deepl.configure', configureSettings)); | ||
context.subscriptions.push(vscode.commands.registerCommand('deepl.translate', translate)); | ||
context.subscriptions.push(vscode.commands.registerCommand('deepl.translateTo', translateTo)); | ||
context.subscriptions.push(vscode.commands.registerCommand('deepl.translateFromTo', translateFromTo)); | ||
context.subscriptions.push(vscode.commands.registerCommand('deepl.translateBelow', translateBelow)); | ||
context.subscriptions.push(vscode.commands.registerCommand(COMMAND_CONFIGURE, configureSettings)); | ||
context.subscriptions.push(vscode.commands.registerCommand(COMMAND_TRANSLATE, translate)); | ||
context.subscriptions.push(vscode.commands.registerCommand(COMMAND_TRANSLATE_TO, translateTo)); | ||
context.subscriptions.push(vscode.commands.registerCommand(COMMAND_TRANSLATE_FROM_TO, translateFromTo)); | ||
context.subscriptions.push(vscode.commands.registerCommand(COMMAND_TRANSLATE_BELOW, translateBelow)); | ||
} | ||
|
||
export function deactivate() {} |
Oops, something went wrong.