Skip to content

Commit

Permalink
Improved usability and stability (#41)
Browse files Browse the repository at this point in the history
* Removed configuration 'deepl.usePro' to improve usability

* Added validation for source & target language

* Unset source language if source language equals the target language
  • Loading branch information
soerenuhrbach authored Dec 20, 2023
1 parent c79a649 commit 41107ee
Show file tree
Hide file tree
Showing 11 changed files with 889 additions and 785 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to the "vscode-deepl" extension will be documented in this f

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

### 1.0.8

- You no longer need to explicitly set "deepl.usePro" in the configuration to use the DeepL Pro API because its now determined based on the given api key
- Added validation to avoid the usage of unsupported source / target languages

### 1.0.7

- Added new configuration options "defaultTargetLanguage" and "defaultSourceLanguage" to set global default languages.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ You can store the API key via the following settings.
This extension contributes the following settings:

* `deepl.apiKey`: The key is used to authenticate with the DeepL API. [See offical documentation](https://www.deepl.com/docs-api/accessing-the-api/authentication/)
* `deepl.usePro`: Whether to use the DeepL Pro API - check this option if you use the paid plan.

For this extension to work, the above settings must be made.

Expand Down Expand Up @@ -71,6 +70,11 @@ Dont use this extension if you dont agree with their privacy policy!

## Release Notes

### 1.0.8

- You no longer need to explicitly set "deepl.usePro" in the configuration to use the DeepL Pro API because its now determined based on the given api key
- Added validation to avoid the usage of unsupported source / target languages

### 1.0.7

- Added new configuration options "defaultTargetLanguage" and "defaultSourceLanguage" to set global default languages.
Expand Down
8 changes: 2 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-deepl",
"displayName": "DeepL for Visual Studio Code",
"description": "Easily translate into more than 25 languages directly from your favourite editor using DeepL.",
"version": "1.0.7",
"version": "1.0.8",
"keywords": [
"DeepL",
"Translate",
Expand Down Expand Up @@ -50,11 +50,6 @@
"default": "",
"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.usePro": {
"type": "boolean",
"default": false,
"description": "Whether to use the DeepL Pro API"
},
"deepl.formality": {
"type": "string",
"default": "default",
Expand Down Expand Up @@ -280,6 +275,7 @@
},
"dependencies": {
"axios": "^1.6.0",
"deepl-node": "^1.11.0",
"vue": "^3.1.5",
"xstate": "^4.23.0"
}
Expand Down
27 changes: 15 additions & 12 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as debug from './debug';
import * as vscode from 'vscode';
import { state } from './state';
import { showMessageWithTimeout } from './vscode';
import { showApiKeyInput, showSourceLanguageInput, showTargetLanguageInput, showUseProInput } from "./inputs";
import { showApiKeyInput, showSourceLanguageInput, showTargetLanguageInput } from "./inputs";
import { TranslateCommandParam, TranslateParam } from './types';
import { getDefaultSourceLanguage, getDefaultTargetLanguage } from './helper';

Expand Down Expand Up @@ -57,7 +57,7 @@ function translateSelections(selections: vscode.Selection[], translateParam: Tra
}
});

showMessageWithTimeout(`Translation complete!`, 3000);
showMessageWithTimeout(`Translation completed!`, 3000);
});
};

Expand All @@ -68,15 +68,6 @@ function createTranslateCommand(param: TranslateCommandParam) {
await configureSettings();
}

const sourceLang = askForSourceLang
? await showSourceLanguageInput()
: state.sourceLanguage
? state.sourceLanguage
: null;
if (askForSourceLang && sourceLang) {
state.sourceLanguage = sourceLang ?? getDefaultSourceLanguage();
}

if (askForTargetLang || !state.targetLanguage) {
const targetLanguage = await showTargetLanguageInput();

Expand All @@ -88,6 +79,19 @@ function createTranslateCommand(param: TranslateCommandParam) {
state.targetLanguage = targetLanguage;
}

const sourceLang = askForSourceLang
? await showSourceLanguageInput()
: state.sourceLanguage
? state.sourceLanguage
: null;
if (askForSourceLang && sourceLang) {
state.sourceLanguage = sourceLang ?? getDefaultSourceLanguage();
}

if (state.targetLanguage === state.sourceLanguage) {
state.sourceLanguage = null;
}

const selections = vscode.window.activeTextEditor?.selections?.filter(selection => !selection.isEmpty);
if (!selections || selections.length === 0) {
return;
Expand All @@ -111,5 +115,4 @@ export const configureSettings = async () => {
if (!state.apiKey) {
return;
}
state.usePro = await showUseProInput();
};
52 changes: 26 additions & 26 deletions src/deepl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from 'axios';
import { state } from './state';
import { Language, LanguageType, Translation } from './types';
import { isFreeAccountAuthKey } from 'deepl-node';

/* eslint-disable */
export enum DeepLErrorCodes {
Expand Down Expand Up @@ -59,35 +60,38 @@ type ErrorHandler = (e: DeepLException) => void;
const http = axios.create();
const errorHandlers: ErrorHandler[] = [];

const formalityAllowed: string[] = ["DE", "FR", "IT", "ES", "NL", "PL", "PT-PT", "PT-BR", "RU"];

http.interceptors.request.use((config) => {
config.baseURL = state.usePro
? 'https://api.deepl.com'
: 'https://api-free.deepl.com';
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}`;

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;
}

if (config.params.target_lang && formalityAllowed.includes(config.params.target_lang.toUpperCase())) {
config.params.formality = state.formality;
}
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;
}

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;
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;
}
}

return config;
Expand Down Expand Up @@ -123,12 +127,8 @@ export async function translate(text: string, targetLanguage: string, sourceLang
}

export async function languages(type: LanguageType = 'source'): Promise<Language[]> {
if (state.languages[type].length === 0) {
const response = await http.get('/v2/languages', { params: { type } });
state.languages[type] = response.data as Language[];
}

return state.languages[type];
const response = await http.get('/v2/languages', { params: { type } });
return response.data as Language[];
}

export const addErrorHandler = (handler: ErrorHandler) => errorHandlers.push(handler);
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { configureSettings, translate, translateFromTo, translateTo, translateBe
export async function activate(context: vscode.ExtensionContext) {
deepl.addErrorHandler(e => vscode.window.showErrorMessage(e.message));

setup(context);
await setup(context);

context.subscriptions.push(createStatusBarItem());
context.subscriptions.push(vscode.commands.registerCommand('deepl.configure', configureSettings));
Expand Down
25 changes: 6 additions & 19 deletions src/inputs.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import * as deepl from './deepl';
import * as vscode from 'vscode';
import { LanguageType } from './types';
import { state } from './state';

async function showLanguageInput(options: vscode.QuickPickOptions, type: LanguageType): Promise<string | null> {
const languages = await deepl.languages(type)
.then((languages): vscode.QuickPickItem[] => {
return languages.map(language => ({
label: language.name,
description: language.language
}));
});
const languages = state.languages[type]
.map(language => ({
label: language.name,
description: language.language
}));

return vscode.window.showQuickPick(languages, options)
.then(item => item?.description ?? null);
Expand All @@ -29,15 +27,4 @@ export function showApiKeyInput() {
placeHolder: 'Please enter your DeepL API key',
ignoreFocusOut: true
}).then(x => x ?? null);
}

export function showUseProInput() {
return vscode.window.showQuickPick(
['No', 'Yes'],
{
title: 'Do you want to use the DeepL Pro API?',
placeHolder: 'Do you want to use the DeepL Pro API?',
ignoreFocusOut: true
}
).then(x => x === 'Yes');
}
61 changes: 34 additions & 27 deletions src/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import * as debug from './debug';
import * as deepl from './deepl';
import { ExtensionState } from './types';
import { reactive, watch, ref } from 'vue';
import { getDefaultSourceLanguage, getDefaultTargetLanguage } from './helper';
Expand All @@ -10,7 +11,6 @@ export const state = reactive<ExtensionState>({
targetLanguage: null,
sourceLanguage: null,
apiKey: null,
usePro: false,
tagHandling: "off",
ignoreTags: "",
nonSplittingTags: "",
Expand All @@ -25,17 +25,16 @@ export const state = reactive<ExtensionState>({
glossaryId: ""
});

export function setup(context: vscode.ExtensionContext) {
if (initialized.value) {
return;
}

initialized.value = true;
const fillStateFromConfig = async (config: vscode.WorkspaceConfiguration, context: vscode.ExtensionContext) => {
state.apiKey = config.get('apiKey') ?? null;

const config = vscode.workspace.getConfiguration('deepl');
if (state.languages.source.length < 1 && !!state.apiKey) {
state.languages.source = await deepl.languages('source');
}
if (state.languages.target.length < 1 && !!state.apiKey) {
state.languages.target = await deepl.languages('target');
}

state.usePro = config.get('usePro') ?? false;
state.apiKey = config.get('apiKey') ?? null;
state.formality = config.get('formality') ?? "default";
state.ignoreTags = config.get('ignoreTags') ?? "";
state.tagHandling = config.get('tagHandling') ?? "off";
Expand All @@ -44,13 +43,32 @@ export function setup(context: vscode.ExtensionContext) {
state.nonSplittingTags = config.get('nonSplittingTags') ?? "";
state.preserveFormatting = config.get('preserveFormatting') ?? false;
state.glossaryId = config.get('glossaryId') ?? "";
state.targetLanguage = context.workspaceState.get<string>('deepl:targetLanguage') ?? getDefaultTargetLanguage(config);
state.sourceLanguage = context.workspaceState.get<string>('deepl:sourceLanguage') ?? getDefaultSourceLanguage(config);

const targetLanguage = context.workspaceState.get<string>('deepl:targetLanguage') ?? getDefaultTargetLanguage(config);
state.targetLanguage = targetLanguage && state.languages.target.map(x => x.language.toLowerCase()).includes(targetLanguage.toLowerCase())
? targetLanguage
: null;

const sourceLanguage = context.workspaceState.get<string>('deepl:sourceLanguage') ?? getDefaultSourceLanguage(config);
state.sourceLanguage = sourceLanguage && state.languages.source.map(x => x.language.toLowerCase()).includes(sourceLanguage.toLowerCase())
? sourceLanguage
: null;
};

export async function setup(context: vscode.ExtensionContext) {
if (initialized.value) {
return;
}

initialized.value = true;

const config = vscode.workspace.getConfiguration('deepl');

await fillStateFromConfig(config, context);

debug.write(`Initialized extension using state:`);
debug.write(JSON.stringify(state, null, 2));

watch(() => state.usePro, () => config.update('usePro', state.usePro, vscode.ConfigurationTarget.Global));
watch(() => state.apiKey, () => config.update('apiKey', state.apiKey, vscode.ConfigurationTarget.Global));
watch(() => state.formality, () => config.update('formality', state.formality, vscode.ConfigurationTarget.Global));
watch(() => state.ignoreTags, () => config.update('ignoreTags', state.ignoreTags, vscode.ConfigurationTarget.Global));
Expand All @@ -70,20 +88,9 @@ export function setup(context: vscode.ExtensionContext) {

debug.write(`Extension configuration has changed! Updating extension state...`);

const { usePro, apiKey, formality, splitSentences, tagHandling, ignoreTags, preserveFormatting, splittingTags, nonSplittingTags, glossaryId, defaultTargetLanguage, defaultSourceLanguage } = vscode.workspace.getConfiguration('deepl');

state.usePro = usePro;
state.apiKey = apiKey;
state.formality = formality;
state.ignoreTags = ignoreTags;
state.tagHandling = tagHandling;
state.splittingTags = splittingTags;
state.splitSentences = splitSentences;
state.nonSplittingTags = nonSplittingTags;
state.preserveFormatting = preserveFormatting;
state.glossaryId = glossaryId;
state.targetLanguage = context.workspaceState.get<string>('deepl:targetLanguage') ?? defaultTargetLanguage ?? null;
state.sourceLanguage = context.workspaceState.get<string>('deepl:sourceLanguage') ?? defaultSourceLanguage ?? null;
const config = vscode.workspace.getConfiguration('deepl');

fillStateFromConfig(config, context);

debug.write(`Updated extension state to:`);
debug.write(JSON.stringify(state, null, 2));
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export interface Language {
language: string;
name: string;
/* eslint-disable-next-line */
supports_formality?: boolean;
};

export interface Translation {
Expand All @@ -15,7 +17,6 @@ export interface ExtensionState {
targetLanguage: string | null,
sourceLanguage: string | null,
apiKey: string | null,
usePro: boolean,
tagHandling: 'html' | 'xml' | 'off',
ignoreTags: string,
formality: string,
Expand Down
2 changes: 1 addition & 1 deletion src/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const sleep = (timeout: number) => new Promise(resolve => {
export const waitFor = async (timeout: number, condition: () => boolean): Promise<boolean> => {
while (!condition() && timeout > 0) {
timeout -= 100;
await sleep(100)
await sleep(100);
}

return timeout > 0 ? true : false;
Expand Down
Loading

0 comments on commit 41107ee

Please sign in to comment.