Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added translate and paste from clipboard command #52

Merged
merged 4 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
40 changes: 25 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 @@ -189,13 +203,14 @@
"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",
"shortTitle": "Configure extension"
},
{
"command": "deepl.configure",
"title": "DeepL: Configure extension"
"command": "deepl.translateAndPasteFromClipboard",
"title": "DeepL: Translate and paste from clipboard",
"shortTitle": "Translate and paste from clipboard"
}
],
"menus": {
Expand All @@ -214,11 +229,6 @@
"when": "editorHasSelection && !isInDiffEditor",
"command": "deepl.translateFromTo",
"group": "deepl@3"
},
{
"when": "editorHasSelection && !isInDiffEditor",
"command": "deepl.translateBelow",
"group": "deepl@3"
}
]
},
Expand All @@ -240,10 +250,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
174 changes: 110 additions & 64 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,62 @@ 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 was 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: (progress: 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 (progress) => {
const increment = 100 / 2 / selections.length;

const texts = selections.map(selection => vscode.window.activeTextEditor?.document.getText(selection));
Expand All @@ -21,20 +69,16 @@ function translateSelections(selections: vscode.Selection[], request: { targetLa
}

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}'`
);
const result = await deepl.translate(text, sourceLanguage, targetLanguage);
progress.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);
editor.replace(selection, replacement ?? translation.text);
}

progress.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 (progress) => {
const increment = 100 / 2 / selections.length;
const translatedClipboardText = await deepl.translate(clipboardText, sourceLanguage, targetLanguage);
progress.report({ increment: increment * selections.length });

vscode.window.activeTextEditor?.edit((editor: vscode.TextEditorEdit) => {
for (const selection of selections) {
editor.replace(selection, translatedClipboardText.text);
progress.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
Loading