From ae7aebb427ae2174facef1a4b73a77d3b662f44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Uhrbach?= Date: Sun, 3 Mar 2024 08:00:37 +0100 Subject: [PATCH 1/4] Added translate and paste from clipboard command --- CHANGELOG.md | 7 ++ README.md | 10 +- package.json | 38 +++--- src/commands.ts | 178 ++++++++++++++++++----------- src/constants.ts | 4 +- src/debug.ts | 2 +- src/deepl.ts | 65 ++++++++++- src/extension.ts | 7 +- src/helper.ts | 4 +- src/{inputs.ts => prompts.ts} | 12 +- src/state.ts | 10 +- src/status-bar.ts | 8 +- src/types.ts | 7 ++ src/{vscode.ts => vscode-utils.ts} | 0 14 files changed, 244 insertions(+), 108 deletions(-) rename src/{inputs.ts => prompts.ts} (53%) rename src/{vscode.ts => vscode-utils.ts} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b335cb8..0d19157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index bd456e1..dcf9d4d 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 @@ -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 diff --git a/package.json b/package.json index acfaa1f..8811392 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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": { @@ -214,11 +227,6 @@ "when": "editorHasSelection && !isInDiffEditor", "command": "deepl.translateFromTo", "group": "deepl@3" - }, - { - "when": "editorHasSelection && !isInDiffEditor", - "command": "deepl.translateBelow", - "group": "deepl@3" } ] }, @@ -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" } ] }, diff --git a/src/commands.ts b/src/commands.ts index b42075c..93e71f2 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -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 { - 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) { 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 { + 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; }) @@ -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 }); + } + }); + }); +}; \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index 562d8cc..bc859aa 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -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`; @@ -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'; \ No newline at end of file +export const COMMAND_TRANSLATE_AND_PASTE_FROM_CLIPBOARD = 'deepl.translateAndPasteFromClipboard'; +export const COMMAND_SET_TARGET_LANGAUGE = 'deepl.setTargetLanguage'; \ No newline at end of file diff --git a/src/debug.ts b/src/debug.ts index a8dda17..af6571f 100644 --- a/src/debug.ts +++ b/src/debug.ts @@ -1,4 +1,4 @@ import * as vscode from 'vscode'; const debugOutputChannel = vscode.window.createOutputChannel("DeepL"); -export const write = (log: string) => debugOutputChannel.appendLine(log); \ No newline at end of file +export const write = (log: string) => debugOutputChannel.appendLine(`[${new Date().toISOString()}]: ${log}`); \ No newline at end of file diff --git a/src/deepl.ts b/src/deepl.ts index bc663e6..562b07f 100644 --- a/src/deepl.ts +++ b/src/deepl.ts @@ -1,7 +1,16 @@ import * as vscode from 'vscode'; +import * as debug from './debug'; import { state } from './state'; import { Language, SourceLanguageCode, TargetLanguageCode, TextResult, Translator } from 'deepl-node'; import { EXTENSION_IDENTIFIER } from './constants'; +import { showSourceLanguagePrompt, showTargetLanguagePrompt } from './prompts'; + +enum ResolveUnsuccessfulTranslationActions { + // eslint-disable-next-line @typescript-eslint/naming-convention + SELECT_SOURCE_LANGUAGE = 'Select source language and try again', + // eslint-disable-next-line @typescript-eslint/naming-convention + CHANGE_TARGET_LANGUAGE = 'Change target language and try again' +} const cache = { targetLanguages: [] as readonly Language[], @@ -22,12 +31,46 @@ const createTranslator = (apiKey: string) => { }); }; -export function translate(texts: T, sourceLang: SourceLanguageCode | null, targetLang: TargetLanguageCode): Promise { +const handleTranslationFailure = async (texts: T, results: T extends string ? TextResult : TextResult[], sourceLanguage: SourceLanguageCode | undefined, targetLanguage: TargetLanguageCode, retries: number = 1) => { + debug.write('The translation result is the with the original input'); + + const selectedAction = await vscode.window.showWarningMessage( + 'Translation was not successful!', + { + detail: sourceLanguage + ? `Please check if you are using the correct source and target language. \n\n Source language: '${sourceLanguage}' \n Target language: '${targetLanguage}'` + : `It is possible that the source language could not be recognized correctly or the wrong target language has been selected. \n\n Target language: '${targetLanguage}'`, + modal: true + }, + ResolveUnsuccessfulTranslationActions.SELECT_SOURCE_LANGUAGE, + ResolveUnsuccessfulTranslationActions.CHANGE_TARGET_LANGUAGE); + + if (selectedAction === ResolveUnsuccessfulTranslationActions.SELECT_SOURCE_LANGUAGE) { + const newSourceLanguage = await showSourceLanguagePrompt(); + if (newSourceLanguage) { + debug.write(`Retrying translation with new source language '${newSourceLanguage}'`); + return await translate(texts, newSourceLanguage, targetLanguage, retries - 1); + } + } + + if (selectedAction === ResolveUnsuccessfulTranslationActions.CHANGE_TARGET_LANGUAGE) { + const newTargetLanguage = await showTargetLanguagePrompt(); + if (newTargetLanguage) { + state.targetLanguage = newTargetLanguage; + debug.write(`Retrying translation with changed target language '${newTargetLanguage}'`); + return await translate(texts, sourceLanguage, newTargetLanguage, retries - 1); + } + } + + return results; +}; + +export async function translate(texts: T, sourceLanguage: SourceLanguageCode | undefined, targetLanguage: TargetLanguageCode, retries: number = 1): Promise { const translator = createTranslator(state.apiKey!); - return translator.translateText( + const results = await translator.translateText( texts, - sourceLang ?? null, - targetLang, + sourceLanguage ?? null, + targetLanguage, { formality: state.formality || undefined, glossary: state.glossaryId || undefined, @@ -39,10 +82,19 @@ export function translate(texts: T, sourceLang: Sou tagHandling: state.tagHandling || undefined } ); + + const wasTranslationSuccessful = typeof texts === "string" + ? (results as TextResult).text !== texts + : (results as TextResult[]).filter((result, index) => result.text === texts[index]).length === 0; + + return !wasTranslationSuccessful && retries > 0 + ? handleTranslationFailure(texts, results, sourceLanguage, targetLanguage, retries) + : results; } export async function getTargetLanguages() { if (!state.apiKey) { + debug.write('Could not load target languages - api key is not configured!'); return []; } @@ -50,7 +102,7 @@ export async function getTargetLanguages() { return cache.targetLanguages; } - const translator = createTranslator(state.apiKey!); + const translator = createTranslator(state.apiKey); const languages = await translator.getTargetLanguages(); cache.targetLanguages = languages; return languages; @@ -58,6 +110,7 @@ export async function getTargetLanguages() { export async function getSourceLanguages() { if (!state.apiKey) { + debug.write('Could not load source languages - api key is not configured!'); return []; } @@ -65,7 +118,7 @@ export async function getSourceLanguages() { return cache.sourceLanguages; } - const translator = createTranslator(state.apiKey!); + const translator = createTranslator(state.apiKey); const languages = await translator.getSourceLanguages(); cache.sourceLanguages = languages; return languages; diff --git a/src/extension.ts b/src/extension.ts index 15346f7..b5da28e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,8 +1,8 @@ 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'; +import { configureSettings, translate, translateFromTo, translateTo, translateAndPasteFromClipboard, setTargetLangauge } from './commands'; +import { COMMAND_CONFIGURE, COMMAND_TRANSLATE, COMMAND_TRANSLATE_FROM_TO, COMMAND_TRANSLATE_TO, COMMAND_TRANSLATE_AND_PASTE_FROM_CLIPBOARD, COMMAND_SET_TARGET_LANGAUGE } from './constants'; export async function activate(context: vscode.ExtensionContext) { await setup(context); @@ -12,7 +12,8 @@ export async function activate(context: vscode.ExtensionContext) { 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)); + context.subscriptions.push(vscode.commands.registerCommand(COMMAND_TRANSLATE_AND_PASTE_FROM_CLIPBOARD, translateAndPasteFromClipboard)); + context.subscriptions.push(vscode.commands.registerCommand(COMMAND_SET_TARGET_LANGAUGE , setTargetLangauge)); } export function deactivate() {} diff --git a/src/helper.ts b/src/helper.ts index 5437518..72b0ead 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -4,10 +4,10 @@ import { SourceLanguageCode, TargetLanguageCode } from 'deepl-node'; export function getDefaultTargetLanguage(config?: vscode.WorkspaceConfiguration): TargetLanguageCode | undefined { config = config ?? vscode.workspace.getConfiguration(); - return config.get(CONFIG_DEFAULT_TARGET_LANGUAGE); + return config.get(CONFIG_DEFAULT_TARGET_LANGUAGE) || undefined; } export function getDefaultSourceLanguage(config?: vscode.WorkspaceConfiguration): SourceLanguageCode | undefined { config = config ?? vscode.workspace.getConfiguration(); - return config.get(CONFIG_DEFAULT_SOURCE_LANGUAGE); + return config.get(CONFIG_DEFAULT_SOURCE_LANGUAGE) || undefined; } \ No newline at end of file diff --git a/src/inputs.ts b/src/prompts.ts similarity index 53% rename from src/inputs.ts rename to src/prompts.ts index 866d098..42a573d 100644 --- a/src/inputs.ts +++ b/src/prompts.ts @@ -2,23 +2,23 @@ import * as vscode from 'vscode'; import * as deepl from './deepl'; import { Language, SourceLanguageCode, TargetLanguageCode } from 'deepl-node'; -function showLanguageInput(options: vscode.QuickPickOptions, languages: readonly Language[]): Thenable { +function showLanguagePrompt(options: vscode.QuickPickOptions, languages: readonly Language[]): Thenable { const items = languages.map(x => ({ label: x.name, description: x.code })); return vscode.window.showQuickPick(items, options) .then(item => item?.description as T | undefined); } -export async function showTargetLanguageInput() { +export async function showTargetLanguagePrompt() { const languages = await deepl.getTargetLanguages(); - return showLanguageInput({ placeHolder: 'Select the language you want to translate into' }, languages); + return showLanguagePrompt({ placeHolder: 'Select the language you want to translate into' }, languages); } -export async function showSourceLanguageInput() { +export async function showSourceLanguagePrompt() { const languages = await deepl.getSourceLanguages(); - return showLanguageInput({ placeHolder: 'Select the language you want to translate from' }, languages); + return showLanguagePrompt({ placeHolder: 'Select the language you want to translate from' }, languages); } -export function showApiKeyInput() { +export function showApiKeyPrompt() { return vscode.window.showInputBox({ title: 'Please enter your DeepL API key', placeHolder: 'Please enter your DeepL API key', diff --git a/src/state.ts b/src/state.ts index 0e045e3..09b3e0b 100644 --- a/src/state.ts +++ b/src/state.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import * as deepl from './deepl'; import * as debug from './debug'; -import type { ExtensionState } from './types'; +import { TranslationMode, type ExtensionState } from './types'; import { reactive, effect, ref } from '@vue/reactivity'; import { getDefaultSourceLanguage, getDefaultTargetLanguage } from './helper'; import { @@ -16,7 +16,8 @@ import { CONFIG_PRESERVE_FORMATTING, CONFIG_GLOSSARY_ID, WORKSPACE_TARGET_LANGUAGE, - WORKSPACE_SOURCE_LANGUAGE + WORKSPACE_SOURCE_LANGUAGE, + CONFIG_TRANSLATION_MODE } from './constants'; import { SourceLanguageCode, TargetLanguageCode } from 'deepl-node'; @@ -33,7 +34,8 @@ export const state = reactive({ preserveFormatting: undefined, formality: undefined, splitSentences: undefined, - glossaryId: undefined + glossaryId: undefined, + translationMode: TranslationMode.Replace }); const loadExtensionState = async (context: vscode.ExtensionContext) => { @@ -48,6 +50,7 @@ const loadExtensionState = async (context: vscode.ExtensionContext) => { state.tagHandling = config.get(CONFIG_TAG_HANDLING) || undefined; state.splittingTags = config.get(CONFIG_SPLITTING_TAGS) || undefined; state.splitSentences = config.get(CONFIG_SPLIT_SENTENCES) || undefined; + state.translationMode = config.get(CONFIG_TRANSLATION_MODE) || TranslationMode.Replace; state.nonSplittingTags = config.get(CONFIG_NON_SPLITTING_TAGS) || undefined; state.preserveFormatting = config.get(CONFIG_PRESERVE_FORMATTING) || undefined; @@ -117,6 +120,7 @@ export async function setup(context: vscode.ExtensionContext) { effect(() => config.update(CONFIG_SPLIT_SENTENCES, state.splitSentences, vscode.ConfigurationTarget.Global)); effect(() => config.update(CONFIG_PRESERVE_FORMATTING, state.preserveFormatting, vscode.ConfigurationTarget.Global)); effect(() => config.update(CONFIG_GLOSSARY_ID, state.glossaryId, vscode.ConfigurationTarget.Global)); + effect(() => config.update(CONFIG_TRANSLATION_MODE, state.translationMode, vscode.ConfigurationTarget.Global)); effect(() => context.workspaceState.update(WORKSPACE_TARGET_LANGUAGE, state.targetLanguage)); effect(() => context.workspaceState.update(WORKSPACE_SOURCE_LANGUAGE, state.sourceLanguage)); diff --git a/src/status-bar.ts b/src/status-bar.ts index 39a1430..764fad7 100644 --- a/src/status-bar.ts +++ b/src/status-bar.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import * as deepl from './deepl'; import { state } from './state'; import { effect } from '@vue/reactivity'; -import { COMMAND_CONFIGURE, COMMAND_TRANSLATE_TO } from './constants'; +import { COMMAND_CONFIGURE, COMMAND_SET_TARGET_LANGAUGE } from './constants'; export function createStatusBarItem() { const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); @@ -17,19 +17,19 @@ export function createStatusBarItem() { } if (state.apiKey) { - statusBarItem.command = COMMAND_TRANSLATE_TO; + statusBarItem.command = COMMAND_SET_TARGET_LANGAUGE; statusBarItem.tooltip = 'Select the language you want to translate into'; } if (state.apiKey && !state.targetLanguage) { - statusBarItem.command = COMMAND_TRANSLATE_TO; + statusBarItem.command = COMMAND_SET_TARGET_LANGAUGE; statusBarItem.text = prefix + 'Select language'; } if (state.apiKey && state.targetLanguage) { const languages = await deepl.getTargetLanguages(); const selectedLanguage = languages.find(x => x.code.toLocaleLowerCase() === state.targetLanguage!.toLocaleLowerCase()); - statusBarItem.command = COMMAND_TRANSLATE_TO; + statusBarItem.command = COMMAND_SET_TARGET_LANGAUGE; statusBarItem.text = prefix + (selectedLanguage?.name ?? state.targetLanguage); } }); diff --git a/src/types.ts b/src/types.ts index 977d6a1..d01b62b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,4 +12,11 @@ export interface ExtensionState { splitSentences?: SentenceSplittingMode, preserveFormatting?: boolean, glossaryId?: GlossaryId + translationMode: TranslationMode +} + +export enum TranslationMode { + Replace = "Replace", + InsertLineBelow = "InsertLineBelow", + InsertLineAbove = "InsertLineAbove" } \ No newline at end of file diff --git a/src/vscode.ts b/src/vscode-utils.ts similarity index 100% rename from src/vscode.ts rename to src/vscode-utils.ts From b5468589d78d31cafb4dc39b84fde2d7cabc1499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Uhrbach?= Date: Sun, 3 Mar 2024 08:02:56 +0100 Subject: [PATCH 2/4] Added short titles --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8811392..309d0eb 100644 --- a/package.json +++ b/package.json @@ -204,11 +204,13 @@ }, { "command": "deepl.configure", - "title": "DeepL: Configure extension" + "title": "DeepL: Configure extension", + "shortTitle": "Configure extension" }, { "command": "deepl.translateAndPasteFromClipboard", - "title": "DeepL: Translate and paste from clipboard" + "title": "DeepL: Translate and paste from clipboard", + "shortTitle": "Translate and paste from clipboard" } ], "menus": { From 7d8c85c00e3ad2b51edcece1a85a649189abb70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Uhrbach?= Date: Sun, 3 Mar 2024 08:06:43 +0100 Subject: [PATCH 3/4] minor improvements --- src/commands.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 93e71f2..4751075 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -19,7 +19,7 @@ async function getTargetAndSourceLanguage (request: GetTargetAndSourceLanguageRe targetLanguage = await showTargetLanguagePrompt() ?? state.targetLanguage ?? getDefaultTargetLanguage(); } if (!targetLanguage) { - throw new Error('Translation is not possible, because no target language selected!'); + throw new Error('Translation is not possible, because no target language was selected!'); } let sourceLanguage = state.sourceLanguage ?? getDefaultSourceLanguage(); @@ -47,7 +47,7 @@ async function getTargetAndSourceLanguage (request: GetTargetAndSourceLanguageRe return { targetLanguage, sourceLanguage }; } -function displayTranslationNotification(execute: (progess: vscode.Progress<{ increment: number }>) => Promise | void) { +function displayTranslationNotification(execute: (progress: vscode.Progress<{ increment: number }>) => Promise | void) { return vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Translating' }, async (progress) => { await execute(progress); showMessageWithTimeout(`Translation completed!`, 3000); @@ -57,14 +57,14 @@ function displayTranslationNotification(execute: (progess: vscode.Progress<{ inc function translateSelections(selections: vscode.Selection[], request: { targetLanguage: TargetLanguageCode, sourceLanguage: SourceLanguageCode | undefined }): Thenable { const { targetLanguage, sourceLanguage } = request; - return displayTranslationNotification(async (progess) => { + return displayTranslationNotification(async (progress) => { 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) { - progess.report({ increment }); + progress.report({ increment }); return null; } @@ -74,7 +74,7 @@ function translateSelections(selections: vscode.Selection[], request: { targetLa : `Start translating '${text}' from '${sourceLanguage}' to '${targetLanguage}'` ); const result = await deepl.translate(text, sourceLanguage, targetLanguage); - progess.report({ increment }); + progress.report({ increment }); debug.write( result ? `Successfully translated '${text}' to '${result.text}'! (Source: '${result.detectedSourceLang}', Target: '${targetLanguage}')` @@ -107,10 +107,10 @@ function translateSelections(selections: vscode.Selection[], request: { targetLa replacement = translation.text; } - editor.replace(selection, replacement); + editor.replace(selection, replacement ?? translation.text); } - progess.report({ increment }); + progress.report({ increment }); } }); }); @@ -154,15 +154,15 @@ export const translateAndPasteFromClipboard = async () => { forceTargetLanguagePrompt: false }); - return displayTranslationNotification(async (progess) => { + return displayTranslationNotification(async (progress) => { const increment = 100 / 2 / selections.length; const translatedClipboardText = await deepl.translate(clipboardText, sourceLanguage, targetLanguage); - progess.report({ increment: increment * selections.length }); + progress.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 }); + progress.report({ increment }); } }); }); From ee47c6888b8ef62fd97d0f8abec028e5f0df50b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Uhrbach?= Date: Sun, 3 Mar 2024 08:07:44 +0100 Subject: [PATCH 4/4] eslint-disable-next-line @typescript-eslint/naming-convention --- src/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types.ts b/src/types.ts index d01b62b..f678cf4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,7 +16,10 @@ export interface ExtensionState { } export enum TranslationMode { + // eslint-disable-next-line @typescript-eslint/naming-convention Replace = "Replace", + // eslint-disable-next-line @typescript-eslint/naming-convention InsertLineBelow = "InsertLineBelow", + // eslint-disable-next-line @typescript-eslint/naming-convention InsertLineAbove = "InsertLineAbove" } \ No newline at end of file