Skip to content

Commit

Permalink
Added translate and paste from clipboard command
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenuhrbach committed Mar 3, 2024
1 parent beca298 commit ae7aebb
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 108 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ 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.1.0

- Removed the command "Translate from ... to ... below original text"
- Added new configuration `deepl.configuration` to specify whether the selected text should be replaced with the translation result or inserted into a new line below/above
- Added the command "Deepl: Translate and paste from clipboard" which allows to translate the clipboard content and paste it.
- Show warning message if translation result equals the original text with actions to resolve this conflict.

### 1.0.14

- Minor bug fixes
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The following commands are available to translate texts:
|DeepL: Translate|alt+t|Translates the selected text into the last used target language
|DeepL: Translate to ...|alt+shift+t|Asks for the target language and translates the selected text into the target language
|DeepL: Translate from ... to ...|alt+ctrl+shift+t|Asks for source and target language and translates the selected text from the source language into the target language
|DeepL: Translate below the original ...|ctrl+alt+t|Append translated text below the selected text
|DeepL: Translate and paste from clipboard|ctrl+shift+v|Translates the clipboard content and paste it

The commands are accessible via the command pallette.

Expand Down Expand Up @@ -53,6 +53,7 @@ Optional settings:
* `deepl.ignoreTags`: Comma-separated list of XML tags that indicate text not to be translated.
* `deepl.defaultTargetLanguage`: Specifies the default target language the text should be translated to. The default target language will be overwriten by choosing a different language using the language chooser. (See all available target languages in the [offical documentation](https://www.deepl.com/docs-api/translate-text))
* `deepl.defaultSourceLanguage`: Specifies the default source language the text should be translated to. The default source language will be overwriten by choosing a different language using the language chooser. (See all available source languages in the [offical documentation](https://www.deepl.com/docs-api/translate-text))
* `deepl.translationMode`: Whether the selected text should be replaced with the translation result or inserted into a new line below/above

## Disclaimer

Expand All @@ -66,6 +67,13 @@ Dont use this extension if you dont agree with their privacy policy!

## Release Notes

### 1.1.0

- Removed the command "Translate from ... to ... below original text"
- Added new configuration `deepl.configuration` to specify whether the selected text should be replaced with the translation result or inserted into a new line below/above
- Added the command "Deepl: Translate and paste from clipboard" which allows to translate the clipboard content and paste it.
- Show warning message if translation result equals the original text with actions to resolve this conflict.

### 1.0.14

- Minor bug fixes
Expand Down
38 changes: 23 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-deepl",
"displayName": "Translate with DeepL for Visual Studio Code",
"description": "Easily translate into more than 25 languages directly from your visual studio code editor using DeepL.",
"version": "1.0.14",
"version": "1.1.0",
"keywords": [
"DeepL",
"Translate",
Expand Down Expand Up @@ -42,6 +42,20 @@
{
"title": "DeepL",
"properties": {
"deepl.translationMode": {
"type": "string",
"default": "Replace",
"enum": [
"Replace",
"InsertLineBelow",
"InsertLineAbove"
],
"enumDescriptions": [
"Replaces the text selected with the translation result",
"The translation result will be inserted in a new line below the selected text",
"The translation result will be inserted in a new line above the selected"
]
},
"deepl.formality": {
"type": "string",
"default": "default",
Expand Down Expand Up @@ -188,14 +202,13 @@
"title": "DeepL: Translate from ... to ...",
"shortTitle": "Translate from ..."
},
{
"command": "deepl.translateBelow",
"title": "DeepL: Translate from ... to ... below the original text",
"shortTitle": "Translate below the original ..."
},
{
"command": "deepl.configure",
"title": "DeepL: Configure extension"
},
{
"command": "deepl.translateAndPasteFromClipboard",
"title": "DeepL: Translate and paste from clipboard"
}
],
"menus": {
Expand All @@ -214,11 +227,6 @@
"when": "editorHasSelection && !isInDiffEditor",
"command": "deepl.translateFromTo",
"group": "deepl@3"
},
{
"when": "editorHasSelection && !isInDiffEditor",
"command": "deepl.translateBelow",
"group": "deepl@3"
}
]
},
Expand All @@ -240,10 +248,10 @@
"mac": "cmd+shift+alt+t"
},
{
"command": "deepl.translateBelow",
"when": "editorHasSelection && !isInDiffEditor",
"key": "ctrl+alt+t",
"mac": "cmd+alt+t"
"command": "deepl.translateAndPasteFromClipboard",
"when": "!isInDiffEditor",
"key": "ctrl+shift+v",
"mac": "cmd+shift+v"
}
]
},
Expand Down
178 changes: 112 additions & 66 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,83 @@ import * as debug from './debug';
import * as deepl from './deepl';
import * as vscode from 'vscode';
import { state } from './state';
import { showMessageWithTimeout } from './vscode';
import { showApiKeyInput, showSourceLanguageInput, showTargetLanguageInput } from "./inputs";
import { showMessageWithTimeout } from './vscode-utils';
import { showApiKeyPrompt, showSourceLanguagePrompt, showTargetLanguagePrompt } from "./prompts";
import { getDefaultSourceLanguage, getDefaultTargetLanguage } from './helper';
import { SourceLanguageCode, TargetLanguageCode } from 'deepl-node';
import { TranslationMode } from './types';

function translateSelections(selections: vscode.Selection[], request: { targetLang: TargetLanguageCode, sourceLang: SourceLanguageCode | null, translateBelow: boolean }): Thenable<void> {
const { targetLang, sourceLang, translateBelow } = request;
type GetTargetAndSourceLanguageRequest = {
forceTargetLanguagePrompt: boolean;
forceSourceLanguagePrompt: boolean;
};

async function getTargetAndSourceLanguage (request: GetTargetAndSourceLanguageRequest): Promise<{ targetLanguage: TargetLanguageCode, sourceLanguage: SourceLanguageCode | undefined }> {
let targetLanguage = state.targetLanguage ?? getDefaultTargetLanguage();
if (request.forceTargetLanguagePrompt || !targetLanguage) {
targetLanguage = await showTargetLanguagePrompt() ?? state.targetLanguage ?? getDefaultTargetLanguage();
}
if (!targetLanguage) {
throw new Error('Translation is not possible, because no target language selected!');
}

let sourceLanguage = state.sourceLanguage ?? getDefaultSourceLanguage();
if (request.forceSourceLanguagePrompt) {
sourceLanguage = await showSourceLanguagePrompt();
}
if (!sourceLanguage) {
sourceLanguage = getDefaultSourceLanguage();
}

if (targetLanguage === sourceLanguage || targetLanguage.slice(0, targetLanguage.indexOf('-')) === sourceLanguage) {
sourceLanguage = undefined;
}

if (state.targetLanguage !== targetLanguage) {
debug.write(`Set target language from '${state.targetLanguage}' to '${targetLanguage}'`);
state.targetLanguage = targetLanguage;
}

if (state.sourceLanguage !== sourceLanguage) {
debug.write(`Set source language from '${state.sourceLanguage}' to '${sourceLanguage}'`);
state.sourceLanguage = sourceLanguage ?? undefined;
}

return { targetLanguage, sourceLanguage };
}

function displayTranslationNotification(execute: (progess: vscode.Progress<{ increment: number }>) => Promise<void> | void) {
return vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Translating' }, async (progress) => {
await execute(progress);
showMessageWithTimeout(`Translation completed!`, 3000);
});
}

function translateSelections(selections: vscode.Selection[], request: { targetLanguage: TargetLanguageCode, sourceLanguage: SourceLanguageCode | undefined }): Thenable<void> {
const { targetLanguage, sourceLanguage } = request;

return displayTranslationNotification(async (progess) => {
const increment = 100 / 2 / selections.length;

const texts = selections.map(selection => vscode.window.activeTextEditor?.document.getText(selection));
const translations = await Promise.all(
texts.map(async text => {
if (!text) {
progress.report({ increment });
progess.report({ increment });
return null;
}

debug.write(
sourceLang
? `Start translating '${text}' to '${targetLang}'`
: `Start translating '${text}' from '${sourceLang}' to '${targetLang}'`
);
const result = await deepl.translate(
text,
sourceLang ?? null,
targetLang,
sourceLanguage
? `Start translating '${text}' to '${targetLanguage}'`
: `Start translating '${text}' from '${sourceLanguage}' to '${targetLanguage}'`
);
progress.report({ increment });
const result = await deepl.translate(text, sourceLanguage, targetLanguage);
progess.report({ increment });
debug.write(
result
? `Successfully translated '${text}' to '${result.text}'! (Source: '${result.detectedSourceLang}', Target: '${targetLang}')`
: `'${text}' could be translated to '${targetLang}! (Reason: DeepL-API returned no translation)'`
? `Successfully translated '${text}' to '${result.text}'! (Source: '${result.detectedSourceLang}', Target: '${targetLanguage}')`
: `'${text}' could not be translated to '${targetLanguage}! (Reason: DeepL-API returned no translation)'`
);
return result;
})
Expand All @@ -46,78 +90,80 @@ function translateSelections(selections: vscode.Selection[], request: { targetLa
const translation = translations[index];

if (selection && translation) {
let replacement = translation.text;

if (translateBelow) {
const originalText = vscode.window.activeTextEditor?.document.getText(selection);
replacement = `${originalText}\n${translation.text}`;
let replacement = null;
const selectionText = vscode.window.activeTextEditor?.document.getText(selection);

switch (state.translationMode) {
case TranslationMode.InsertLineAbove:
replacement = `${translation.text}\n${selectionText}`;
break;

case TranslationMode.InsertLineBelow:
replacement = `${selectionText}\n${translation.text}`;
break;

default:
case TranslationMode.Replace:
replacement = translation.text;
}

editor.replace(selection, replacement);
}

progress.report({ increment });
progess.report({ increment });
}
});

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

function createTranslateCommand(request: { askForTargetLang: boolean, askForSourceLang: boolean, translateBelow: boolean }) {
const { askForTargetLang, askForSourceLang, translateBelow } = request;
function createTranslateSelectionsCommand(request: GetTargetAndSourceLanguageRequest) {
return async function () {
if (!state.apiKey) {
await configureSettings();
}

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

if (!targetLanguage) {
state.targetLanguage = getDefaultTargetLanguage();
return;
}

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

const selections = vscode.window.activeTextEditor?.selections?.filter(selection => !selection.isEmpty);
if (!selections || selections.length === 0) {
return;
}

await translateSelections(
selections,
{
targetLang: state.targetLanguage,
sourceLang: sourceLang ?? null,
translateBelow,
}
);
await translateSelections(selections, await getTargetAndSourceLanguage(request));
};
}

export const translate = createTranslateCommand({ askForTargetLang: false, askForSourceLang: false, translateBelow: false });
export const translateTo = createTranslateCommand({ askForTargetLang: true, askForSourceLang: false, translateBelow: false });
export const translateFromTo = createTranslateCommand({ askForTargetLang: true, askForSourceLang: true, translateBelow: false });
export const translateBelow = createTranslateCommand({ askForTargetLang: false, askForSourceLang: false, translateBelow: true });
export const configureSettings = async () => {
state.apiKey = await showApiKeyInput();
if (!state.apiKey) {
export const setTargetLangauge = async () => state.targetLanguage = await showTargetLanguagePrompt();
export const configureSettings = async () => state.apiKey = await showApiKeyPrompt();

export const translate = createTranslateSelectionsCommand({ forceTargetLanguagePrompt: false, forceSourceLanguagePrompt: false });
export const translateTo = createTranslateSelectionsCommand({ forceTargetLanguagePrompt: true, forceSourceLanguagePrompt: false });
export const translateFromTo = createTranslateSelectionsCommand({ forceTargetLanguagePrompt: true, forceSourceLanguagePrompt: true });

export const translateAndPasteFromClipboard = async () => {
const selections = vscode.window.activeTextEditor?.selections;
if (!selections || selections.length === 0) {
return;
}
};

const clipboardText = await vscode.env.clipboard.readText();
if (!clipboardText) {
return;
}

const { targetLanguage, sourceLanguage } = await getTargetAndSourceLanguage({
forceSourceLanguagePrompt: false,
forceTargetLanguagePrompt: false
});

return displayTranslationNotification(async (progess) => {
const increment = 100 / 2 / selections.length;
const translatedClipboardText = await deepl.translate(clipboardText, sourceLanguage, targetLanguage);
progess.report({ increment: increment * selections.length });

vscode.window.activeTextEditor?.edit((editor: vscode.TextEditorEdit) => {
for (const selection of selections) {
editor.replace(selection, translatedClipboardText.text);
progess.report({ increment });
}
});
});
};
4 changes: 3 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const CONFIG_PRESERVE_FORMATTING = `${DEEPL_CONFIGURATION_SECTION}.preser
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 CONFIG_TRANSLATION_MODE = `${DEEPL_CONFIGURATION_SECTION}.translationMode`;
export const WORKSPACE_TARGET_LANGUAGE = `${DEEPL_CONFIGURATION_SECTION}.targetLanguage`;
export const WORKSPACE_SOURCE_LANGUAGE = `${DEEPL_CONFIGURATION_SECTION}.sourceLanguage`;

Expand All @@ -20,4 +21,5 @@ 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';
export const COMMAND_TRANSLATE_AND_PASTE_FROM_CLIPBOARD = 'deepl.translateAndPasteFromClipboard';
export const COMMAND_SET_TARGET_LANGAUGE = 'deepl.setTargetLanguage';
2 changes: 1 addition & 1 deletion src/debug.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as vscode from 'vscode';
const debugOutputChannel = vscode.window.createOutputChannel("DeepL");

export const write = (log: string) => debugOutputChannel.appendLine(log);
export const write = (log: string) => debugOutputChannel.appendLine(`[${new Date().toISOString()}]: ${log}`);
Loading

0 comments on commit ae7aebb

Please sign in to comment.