Skip to content

Commit

Permalink
Added linguee translation plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverschwendener committed Feb 12, 2019
1 parent bc95cef commit 8c50467
Show file tree
Hide file tree
Showing 16 changed files with 410 additions and 6 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@types/lodash": "^4.14.119",
"@types/node-powershell": "^3.1.0",
"app2png": "https://github.com/oliverschwendener/app2png",
"axios": "^0.18.0",
"bulma-extensions": "^4.0.1",
"electron": "^4.0.0",
"electron-store": "^2.0.0",
Expand Down
11 changes: 11 additions & 0 deletions src/common/config/default-translation-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { TranslationOptions } from "./translation-options";
import { TranslationLanguage } from "../../main/plugins/translation-execution-plugin/translation-language";

export const defaultTranslationOptions: TranslationOptions = {
debounceDelay: 250,
enabled: true,
minSearchTermLength: 3,
prefix: "t?",
sourceLanguage: TranslationLanguage.German,
targetLanguage: TranslationLanguage.English,
};
2 changes: 2 additions & 0 deletions src/common/config/default-user-config-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { defaultAppearanceOptions } from "./default-appearance-options";
import { defaultShortcutOptions } from "./default-shortcuts-options";
import { defaultEverythingSearchOptions } from "./default-everything-search-options";
import { defaultMdfindOptions } from "./default-mdfind-options";
import { defaultTranslationOptions } from "./default-translation-options";

export const defaultUserConfigOptions: UserConfigOptions = {
appearanceOptions: defaultAppearanceOptions,
Expand All @@ -15,4 +16,5 @@ export const defaultUserConfigOptions: UserConfigOptions = {
mdfindOptions: defaultMdfindOptions,
searchEngineOptions: defaultSearchEngineOptions,
shortcutOptions: defaultShortcutOptions,
translationOptions: defaultTranslationOptions,
};
10 changes: 10 additions & 0 deletions src/common/config/translation-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { TranslationLanguage } from "../../main/plugins/translation-execution-plugin/translation-language";

export interface TranslationOptions {
enabled: boolean;
debounceDelay: number;
minSearchTermLength: number;
prefix: string;
sourceLanguage: TranslationLanguage;
targetLanguage: TranslationLanguage;
}
2 changes: 2 additions & 0 deletions src/common/config/user-config-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AppearanceOptions } from "./appearance-options";
import { ShortcutOptions } from "./shortcuts-options";
import { EverythingSearchOptions } from "./everything-search-options";
import { MdFindOptions } from "./mdfind-options";
import { TranslationOptions } from "./translation-options";

export interface UserConfigOptions {
appearanceOptions: AppearanceOptions;
Expand All @@ -13,5 +14,6 @@ export interface UserConfigOptions {
mdfindOptions: MdFindOptions;
searchEngineOptions: SearchEngineOptions;
shortcutOptions: ShortcutOptions;
translationOptions: TranslationOptions;
generalOptions: GeneralOptions;
}
20 changes: 20 additions & 0 deletions src/common/error-search-result-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SearchResultItem } from "./search-result-item";
import { IconType } from "./icon/icon-type";
import { PluginType } from "../main/plugin-type";

export const errorSearchResultItem: SearchResultItem = {
description: "Check ueli's log to see more details",
executionArgument: "",
hideMainWindowAfterExecution: false,
icon: {
parameter: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32" version="1.1">
<g id="surface1">
<path style=" " d="M 16 3.21875 L 15.125 4.71875 L 3.125 25.5 L 2.28125 27 L 29.71875 27 L 28.875 25.5 L 16.875 4.71875 Z M 16 7.21875 L 26.25 25 L 5.75 25 Z M 15 14 L 15 20 L 17 20 L 17 14 Z M 15 21 L 15 23 L 17 23 L 17 21 Z "></path>
</g>
</svg>`,
type: IconType.SVG,
},
name: "An error occured",
originPluginType: PluginType.None,
searchable: [],
};
7 changes: 6 additions & 1 deletion src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { cloneDeep } from "lodash";
import { GlobalHotKey } from "../common/global-hot-key/global-hot-key";
import { GlobalHotKeyHelpers } from "../common/global-hot-key/global-hot-key-helpers";
import { defaultGeneralOptions } from "../common/config/default-general-options";
import { errorSearchResultItem } from "../common/error-search-result-item";

const logger = new ConsoleLogger();
const configRepository = new ElectronStoreConfigRepository(cloneDeep(defaultUserConfigOptions));
Expand Down Expand Up @@ -258,7 +259,11 @@ function registerAllIpcListeners() {
updateMainWindowSize(result.length, config.appearanceOptions);
event.sender.send(IpcChannels.searchResponse, result);
})
.catch((err) => logger.error(err));
.catch((err) => {
logger.error(err);
updateMainWindowSize(1, config.appearanceOptions);
event.sender.send(IpcChannels.searchResponse, [errorSearchResultItem]);
});
});

ipcMain.on(IpcChannels.execute, (event: Electron.Event, searchResultItem: SearchResultItem, privileged: boolean) => {
Expand Down
2 changes: 2 additions & 0 deletions src/main/plugin-type.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export enum PluginType {
None = "none",
ApplicationSearchPlugin = "application-search-plugin",
UeliCommandSearchPlugin = "ueli-command-search-plugin",
ShortcutsSearchPlugin = "shortcuts-search-plugin",
EverythingSearchPlugin = "everything-search-plugin",
MdFindExecutionPlugin = "md-find-execution-plugin",
TranslationPlugin = "translation-plugin",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { ExecutionPlugin } from "../../execution-plugin";
import { SearchResultItem } from "../../../common/search-result-item";
import { UserConfigOptions } from "../../../common/config/user-config-options";
import { PluginType } from "../../plugin-type";
import axios from "axios";
import { IconType } from "../../../common/icon/icon-type";
import { Icon } from "../../../common/icon/icon";
import { clipboard } from "electron";

interface Translation {
text: string;
}

interface ExactMatch {
translations: Translation[];
}

interface TranslationResponse {
exact_matches?: ExactMatch[];
}

export class TranslationExecutionPlugin implements ExecutionPlugin {
public readonly pluginType = PluginType.TranslationPlugin;
private config: UserConfigOptions;
private readonly defaultIcon: Icon = {
parameter: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32" version="1.1">
<g id="surface1">
<path style=" " d="M 4 4 L 4 22 L 10 22 L 10 28 L 28 28 L 28 10 L 22 10 L 22 4 Z M 6 6 L 20 6 L 20 10.5625 L 10.5625 20 L 6 20 Z M 11 8 L 11 9 L 8 9 L 8 11 L 12.9375 11 C 12.808594 12.148438 12.457031 13.054688 11.875 13.6875 C 11.53125 13.574219 11.222656 13.433594 10.96875 13.28125 C 10.265625 12.863281 10 12.417969 10 12 L 8 12 C 8 13.191406 8.734375 14.183594 9.71875 14.84375 C 9.226563 14.949219 8.65625 15 8 15 L 8 17 C 9.773438 17 11.25 16.59375 12.375 15.84375 C 12.898438 15.933594 13.429688 16 14 16 L 14 14.125 C 14.542969 13.214844 14.832031 12.152344 14.9375 11 L 16 11 L 16 9 L 13 9 L 13 8 Z M 21.4375 12 L 26 12 L 26 26 L 12 26 L 12 21.4375 Z M 20 13.84375 L 19.0625 16.6875 L 17.0625 22.6875 L 17 22.84375 L 17 24 L 19 24 L 19 23.125 L 19.03125 23 L 20.96875 23 L 21 23.125 L 21 24 L 23 24 L 23 22.84375 L 22.9375 22.6875 L 20.9375 16.6875 Z M 20 20.125 L 20.28125 21 L 19.71875 21 Z "></path>
</g>
</svg>`,
type: IconType.SVG,
};
private delay: NodeJS.Timeout | number;

constructor(config: UserConfigOptions) {
this.config = config;
}

public execute(searchResultItem: SearchResultItem): Promise<void> {
return new Promise((resolve) => {
clipboard.writeText(searchResultItem.executionArgument);
resolve();
});
}

public getSearchResults(userInput: string): Promise<SearchResultItem[]> {
return new Promise((resolve, reject) => {
const textToTranslate = userInput.replace(this.config.translationOptions.prefix, "");
const source = this.config.translationOptions.sourceLanguage;
const target = this.config.translationOptions.targetLanguage;
const url = `https://linguee-api.herokuapp.com/api?q=${textToTranslate}&src=${source}&dst=${target}`;

if (this.delay !== undefined) {
clearTimeout(this.delay as number);
}

this.delay = setTimeout(() => {
this.getTranslationResults(url)
.then((result) => resolve(result))
.catch((err) => reject(err));
}, this.config.translationOptions.debounceDelay);
});
}

public isEnabled() {
return this.config.translationOptions.enabled;
}

public isValidUserInput(userInput: string) {
return userInput.startsWith(this.config.translationOptions.prefix)
&& userInput.replace(this.config.translationOptions.prefix, "").length > this.config.translationOptions.minSearchTermLength;
}

public updateConfig(updatedConfig: UserConfigOptions): Promise<void> {
return new Promise((resolve) => {
this.config = updatedConfig;
resolve();
});
}

private getTranslationResults(url: string): Promise<SearchResultItem[]> {
return new Promise((resolve, reject) => {
axios.get(url)
.then((response) => {
const data: TranslationResponse = response.data;
let translationList: string[] = [];
if (data.exact_matches) {
data.exact_matches
.map((exactMatch) => exactMatch.translations.map((t) => t.text))
.forEach((t) => translationList = translationList.concat(t));
}
const result = translationList.map((t): SearchResultItem => {
return {
description: "Press ENTER to copy to clipboard",
executionArgument: t,
hideMainWindowAfterExecution: true,
icon: this.defaultIcon,
name: t,
originPluginType: this.pluginType,
searchable: [],
};
});
if (result.length > 0) {
resolve(result);
} else {
resolve([this.getErrorResult("No translations found")]);
}
})
.catch((err) => {
resolve([this.getErrorResult(err.response.data.message, err.message)]);
});
});
}

private getErrorResult(errorMessage: string, details?: string): SearchResultItem {
return {
description: details ? details : "",
executionArgument: "",
hideMainWindowAfterExecution: false,
icon: this.defaultIcon,
name: errorMessage,
originPluginType: this.pluginType,
searchable: [],
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum TranslationLanguage {
German = "de",
English = "en",
Spanish = "es",
French = "fr",
Portuguese = "pt",
}
5 changes: 4 additions & 1 deletion src/main/production/production-search-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { EverythingExecutionPlugin } from "../plugins/everything-execution-plugi
import { SearchPlugin } from "../search-plugin";
import { ExecutionPlugin } from "../execution-plugin";
import { MdFindExecutionPlugin } from "../plugins/mdfind-execution-plugin/mdfind-execution-plugin";
import { TranslationExecutionPlugin } from "../plugins/translation-execution-plugin/translation-execution-plugin";

const urlExecutor = isWindows(platform()) ? executeUrlWindows : executeUrlMacOs;
const filePathExecutor = isWindows(platform()) ? executeFilePathWindows : executeFilePathMacOs;
Expand All @@ -32,7 +33,9 @@ export const getProductionSearchPlugins = (userConfig: UserConfigOptions): Searc
filePathExecutor),
];

const executionPlugins: ExecutionPlugin[] = [];
const executionPlugins: ExecutionPlugin[] = [
new TranslationExecutionPlugin(userConfig),
];

if (isWindows(platform())) {
executionPlugins.push(new EverythingExecutionPlugin(userConfig, filePathExecutor));
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { cloneDeep } from "lodash";
import { newApplicationFolderModalComponent } from "./new-application-folder-modal-component";
import { newApplicationFileExtensionModalComponent } from "./new-application-fileextension-modal-component";
import { mdfindSettingsComponent } from "./mdfind-settings-component";
import { translationSettingsComponent } from "./translation-settings-component";

Vue.component("user-input", userInputComponent);
Vue.component("search-results", searchResultsComponent);
Expand All @@ -40,6 +41,7 @@ Vue.component("shortcut-editing-modal", shortcutEditingModal);
Vue.component("new-application-folder-modal", newApplicationFolderModalComponent);
Vue.component("new-application-file-extension-modal", newApplicationFileExtensionModalComponent);
Vue.component("mdfind-settings", mdfindSettingsComponent);
Vue.component("translation-settings", translationSettingsComponent);

// tslint:disable-next-line:no-unused-expression
new Vue({
Expand Down
6 changes: 2 additions & 4 deletions src/renderer/settings-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ export const settingsComponent = Vue.extend({
<setting-menu-item v-for="settingMenuItem in settingMenuItems" :name="settingMenuItem"></setting-menu-item>
</ul>
</div>
<div
class="settings__notification notification"
:class="notificationClass"
>
<div class="settings__notification notification" :class="notificationClass">
<button class="delete" @click="removeNotification"></button>
{{ notification.message }}
</div>
Expand All @@ -107,6 +104,7 @@ export const settingsComponent = Vue.extend({
<application-search-settings :config="config"></application-search-settings>
<shortcut-settings :config="config"></shortcut-settings>
<mdfind-settings :config="config"></mdfind-settings>
<translation-settings :config="config"></translation-settings>
</div>
</div>
`,
Expand Down
1 change: 1 addition & 0 deletions src/renderer/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export enum Settings {
General = "General",
SearchEngine = "Search Engine",
Shortcuts = "Shortcuts",
Translation = "Translation",
}
Loading

0 comments on commit 8c50467

Please sign in to comment.