diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/@types/stores.types.ts | 3 | ||||
-rw-r--r-- | src/components/settings/settings/EditSettingsForm.jsx | 12 | ||||
-rw-r--r-- | src/config.ts | 139 | ||||
-rw-r--r-- | src/containers/settings/EditSettingsScreen.tsx | 54 | ||||
-rw-r--r-- | src/helpers/translation-helpers.ts | 47 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 3 | ||||
-rw-r--r-- | src/index.ts | 13 | ||||
-rw-r--r-- | src/models/IContextMenuParams.ts | 7 | ||||
-rw-r--r-- | src/stores/ServicesStore.ts | 21 | ||||
-rw-r--r-- | src/webview/contextMenu.ts | 6 | ||||
-rw-r--r-- | src/webview/contextMenuBuilder.ts | 255 | ||||
-rw-r--r-- | src/webview/recipe.js | 6 |
12 files changed, 540 insertions, 26 deletions
diff --git a/src/@types/stores.types.ts b/src/@types/stores.types.ts index eec18c11e..13870a43a 100644 --- a/src/@types/stores.types.ts +++ b/src/@types/stores.types.ts | |||
@@ -90,6 +90,7 @@ interface AppStore extends TypedStore { | |||
90 | darkMode: boolean; | 90 | darkMode: boolean; |
91 | dictionaries: []; | 91 | dictionaries: []; |
92 | enableSpellchecking: boolean; | 92 | enableSpellchecking: boolean; |
93 | enableTranslator: boolean; | ||
93 | fetchDataInterval: 4; | 94 | fetchDataInterval: 4; |
94 | get(key: string): any; | 95 | get(key: string): any; |
95 | getAppCacheSizeRequest: () => void; | 96 | getAppCacheSizeRequest: () => void; |
@@ -106,6 +107,8 @@ interface AppStore extends TypedStore { | |||
106 | reloadAfterResume: boolean; | 107 | reloadAfterResume: boolean; |
107 | reloadAfterResumeTime: number; | 108 | reloadAfterResumeTime: number; |
108 | searchEngine: string; | 109 | searchEngine: string; |
110 | translatorEngine: string; | ||
111 | translatorLanguage: string; | ||
109 | spellcheckerLanguage: string; | 112 | spellcheckerLanguage: string; |
110 | splitMode: boolean; | 113 | splitMode: boolean; |
111 | splitColumns: number; | 114 | splitColumns: number; |
diff --git a/src/components/settings/settings/EditSettingsForm.jsx b/src/components/settings/settings/EditSettingsForm.jsx index 0e5be38ed..e6cba922b 100644 --- a/src/components/settings/settings/EditSettingsForm.jsx +++ b/src/components/settings/settings/EditSettingsForm.jsx | |||
@@ -870,12 +870,24 @@ class EditSettingsForm extends Component { | |||
870 | {intl.formatMessage(messages.spellCheckerLanguageInfo)} | 870 | {intl.formatMessage(messages.spellCheckerLanguageInfo)} |
871 | </p> | 871 | </p> |
872 | )} | 872 | )} |
873 | |||
873 | <p className="settings__help"> | 874 | <p className="settings__help"> |
874 | {intl.formatMessage(messages.appRestartRequired)} | 875 | {intl.formatMessage(messages.appRestartRequired)} |
875 | </p> | 876 | </p> |
876 | 877 | ||
877 | <Hr /> | 878 | <Hr /> |
878 | 879 | ||
880 | <Toggle field={form.$('enableTranslator')} /> | ||
881 | |||
882 | {form.$('enableTranslator').value && ( | ||
883 | <Select field={form.$('translatorEngine')} /> | ||
884 | )} | ||
885 | {form.$('enableTranslator').value && ( | ||
886 | <Select field={form.$('translatorLanguage')} /> | ||
887 | )} | ||
888 | |||
889 | <Hr /> | ||
890 | |||
879 | <a | 891 | <a |
880 | href={FERDIUM_TRANSLATION} | 892 | href={FERDIUM_TRANSLATION} |
881 | target="_blank" | 893 | target="_blank" |
diff --git a/src/config.ts b/src/config.ts index 150b7101d..c8cb09d84 100644 --- a/src/config.ts +++ b/src/config.ts | |||
@@ -24,6 +24,8 @@ export const LIVE_WS_API = 'wss://api.franzinfra.com'; | |||
24 | export const LOCAL_API_WEBSITE = 'http://localhost:3333'; | 24 | export const LOCAL_API_WEBSITE = 'http://localhost:3333'; |
25 | export const DEV_API_FRANZ_WEBSITE = 'https://meetfranz.com'; | 25 | export const DEV_API_FRANZ_WEBSITE = 'https://meetfranz.com'; |
26 | export const LIVE_API_FERDIUM_WEBSITE = 'https://ferdium.org'; | 26 | export const LIVE_API_FERDIUM_WEBSITE = 'https://ferdium.org'; |
27 | export const LIVE_API_FERDIUM_LIBRETRANSLATE = | ||
28 | 'https://translator.ferdium.org/translate'; | ||
27 | 29 | ||
28 | export const STATS_API = 'https://stats.franzinfra.com'; | 30 | export const STATS_API = 'https://stats.franzinfra.com'; |
29 | 31 | ||
@@ -84,6 +86,140 @@ export const SEARCH_ENGINE_NAMES = { | |||
84 | [SEARCH_ENGINE_DDG]: 'DuckDuckGo', | 86 | [SEARCH_ENGINE_DDG]: 'DuckDuckGo', |
85 | }; | 87 | }; |
86 | 88 | ||
89 | export const TRANSLATOR_ENGINE_GOOGLE = 'Google'; | ||
90 | export const TRANSLATOR_ENGINE_LIBRETRANSLATE = 'LibreTranslate'; | ||
91 | export const TRANSLATOR_ENGINE_NAMES = { | ||
92 | [TRANSLATOR_ENGINE_LIBRETRANSLATE]: | ||
93 | 'Ferdium Translator (Powered by LibreTranslate)', | ||
94 | [TRANSLATOR_ENGINE_GOOGLE]: 'Google', | ||
95 | }; | ||
96 | |||
97 | export const LIBRETRANSLATE_TRANSLATOR_LANGUAGES = { | ||
98 | ar: 'Arabic', | ||
99 | zh: 'Chinese', | ||
100 | en: 'English', | ||
101 | fr: 'French', | ||
102 | de: 'German', | ||
103 | hi: 'Hindi', | ||
104 | id: 'Indonesian', | ||
105 | ga: 'Irish', | ||
106 | it: 'Italian', | ||
107 | ja: 'Japanese', | ||
108 | ko: 'Korean', | ||
109 | pl: 'Polish', | ||
110 | pt: 'Portuguese', | ||
111 | ru: 'Russian', | ||
112 | es: 'Spanish', | ||
113 | tr: 'Turkish', | ||
114 | vi: 'Vietnamese', | ||
115 | }; | ||
116 | |||
117 | export const GOOGLE_TRANSLATOR_LANGUAGES = { | ||
118 | af: 'Afrikaans', | ||
119 | sq: 'Albanian', | ||
120 | ar: 'Arabic', | ||
121 | hy: 'Armenian', | ||
122 | az: 'Azerbaijani', | ||
123 | eu: 'Basque', | ||
124 | be: 'Belarusian', | ||
125 | bn: 'Bengali', | ||
126 | bs: 'Bosnian', | ||
127 | bg: 'Bulgarian', | ||
128 | ca: 'Catalan', | ||
129 | ceb: 'Cebuano', | ||
130 | ny: 'Chichewa', | ||
131 | 'zh-cn': 'Chinese Simplified', | ||
132 | 'zh-tw': 'Chinese Traditional', | ||
133 | co: 'Corsican', | ||
134 | hr: 'Croatian', | ||
135 | cs: 'Czech', | ||
136 | da: 'Danish', | ||
137 | nl: 'Dutch', | ||
138 | en: 'English', | ||
139 | eo: 'Esperanto', | ||
140 | et: 'Estonian', | ||
141 | tl: 'Filipino', | ||
142 | fi: 'Finnish', | ||
143 | fr: 'French', | ||
144 | fy: 'Frisian', | ||
145 | gl: 'Galician', | ||
146 | ka: 'Georgian', | ||
147 | de: 'German', | ||
148 | el: 'Greek', | ||
149 | gu: 'Gujarati', | ||
150 | ht: 'Haitian Creole', | ||
151 | ha: 'Hausa', | ||
152 | haw: 'Hawaiian', | ||
153 | iw: 'Hebrew', | ||
154 | hi: 'Hindi', | ||
155 | hmn: 'Hmong', | ||
156 | hu: 'Hungarian', | ||
157 | is: 'Icelandic', | ||
158 | ig: 'Igbo', | ||
159 | id: 'Indonesian', | ||
160 | ga: 'Irish', | ||
161 | it: 'Italian', | ||
162 | ja: 'Japanese', | ||
163 | jw: 'Javanese', | ||
164 | kn: 'Kannada', | ||
165 | kk: 'Kazakh', | ||
166 | km: 'Khmer', | ||
167 | ko: 'Korean', | ||
168 | ku: 'Kurdish (Kurmanji)', | ||
169 | ky: 'Kyrgyz', | ||
170 | lo: 'Lao', | ||
171 | la: 'Latin', | ||
172 | lv: 'Latvian', | ||
173 | lt: 'Lithuanian', | ||
174 | lb: 'Luxembourgish', | ||
175 | mk: 'Macedonian', | ||
176 | mg: 'Malagasy', | ||
177 | ms: 'Malay', | ||
178 | ml: 'Malayalam', | ||
179 | mt: 'Maltese', | ||
180 | mi: 'Maori', | ||
181 | mr: 'Marathi', | ||
182 | mn: 'Mongolian', | ||
183 | my: 'Myanmar (Burmese)', | ||
184 | ne: 'Nepali', | ||
185 | no: 'Norwegian', | ||
186 | ps: 'Pashto', | ||
187 | fa: 'Persian', | ||
188 | pl: 'Polish', | ||
189 | pt: 'Portuguese', | ||
190 | ma: 'Punjabi', | ||
191 | ro: 'Romanian', | ||
192 | ru: 'Russian', | ||
193 | sm: 'Samoan', | ||
194 | gd: 'Scots Gaelic', | ||
195 | sr: 'Serbian', | ||
196 | st: 'Sesotho', | ||
197 | sn: 'Shona', | ||
198 | sd: 'Sindhi', | ||
199 | si: 'Sinhala', | ||
200 | sk: 'Slovak', | ||
201 | sl: 'Slovenian', | ||
202 | so: 'Somali', | ||
203 | es: 'Spanish', | ||
204 | su: 'Sudanese', | ||
205 | sw: 'Swahili', | ||
206 | sv: 'Swedish', | ||
207 | tg: 'Tajik', | ||
208 | ta: 'Tamil', | ||
209 | te: 'Telugu', | ||
210 | th: 'Thai', | ||
211 | tr: 'Turkish', | ||
212 | uk: 'Ukrainian', | ||
213 | ur: 'Urdu', | ||
214 | uz: 'Uzbek', | ||
215 | vi: 'Vietnamese', | ||
216 | cy: 'Welsh', | ||
217 | xh: 'Xhosa', | ||
218 | yi: 'Yiddish', | ||
219 | yo: 'Yoruba', | ||
220 | zu: 'Zulu', | ||
221 | }; | ||
222 | |||
87 | export const SEARCH_ENGINE_URLS = { | 223 | export const SEARCH_ENGINE_URLS = { |
88 | [SEARCH_ENGINE_STARTPAGE]: ({ searchTerm }) => | 224 | [SEARCH_ENGINE_STARTPAGE]: ({ searchTerm }) => |
89 | `https://www.startpage.com/sp/search?query=${searchTerm}`, | 225 | `https://www.startpage.com/sp/search?query=${searchTerm}`, |
@@ -222,6 +358,7 @@ export const DEFAULT_APP_SETTINGS = { | |||
222 | showMessageBadgeWhenMuted: true, | 358 | showMessageBadgeWhenMuted: true, |
223 | showDragArea: false, | 359 | showDragArea: false, |
224 | enableSpellchecking: true, | 360 | enableSpellchecking: true, |
361 | enableTranslator: false, | ||
225 | spellcheckerLanguage: 'en-us', | 362 | spellcheckerLanguage: 'en-us', |
226 | darkMode: false, | 363 | darkMode: false, |
227 | navigationBarManualActive: false, | 364 | navigationBarManualActive: false, |
@@ -261,6 +398,8 @@ export const DEFAULT_APP_SETTINGS = { | |||
261 | iconSize: iconSizeBias, | 398 | iconSize: iconSizeBias, |
262 | navigationBarBehaviour: 'custom', | 399 | navigationBarBehaviour: 'custom', |
263 | searchEngine: SEARCH_ENGINE_STARTPAGE, | 400 | searchEngine: SEARCH_ENGINE_STARTPAGE, |
401 | translatorLanguage: 'en', | ||
402 | translatorEngine: TRANSLATOR_ENGINE_LIBRETRANSLATE, | ||
264 | useHorizontalStyle: false, | 403 | useHorizontalStyle: false, |
265 | hideCollapseButton: false, | 404 | hideCollapseButton: false, |
266 | isMenuCollapsed: false, | 405 | isMenuCollapsed: false, |
diff --git a/src/containers/settings/EditSettingsScreen.tsx b/src/containers/settings/EditSettingsScreen.tsx index fbbed629a..a4d7ba0eb 100644 --- a/src/containers/settings/EditSettingsScreen.tsx +++ b/src/containers/settings/EditSettingsScreen.tsx | |||
@@ -15,6 +15,10 @@ import { | |||
15 | ICON_SIZES, | 15 | ICON_SIZES, |
16 | NAVIGATION_BAR_BEHAVIOURS, | 16 | NAVIGATION_BAR_BEHAVIOURS, |
17 | SEARCH_ENGINE_NAMES, | 17 | SEARCH_ENGINE_NAMES, |
18 | TRANSLATOR_ENGINE_NAMES, | ||
19 | GOOGLE_TRANSLATOR_LANGUAGES, | ||
20 | TRANSLATOR_ENGINE_GOOGLE, | ||
21 | LIBRETRANSLATE_TRANSLATOR_LANGUAGES, | ||
18 | TODO_APPS, | 22 | TODO_APPS, |
19 | DEFAULT_SETTING_KEEP_ALL_WORKSPACES_LOADED, | 23 | DEFAULT_SETTING_KEEP_ALL_WORKSPACES_LOADED, |
20 | DEFAULT_IS_FEATURE_ENABLED_BY_USER, | 24 | DEFAULT_IS_FEATURE_ENABLED_BY_USER, |
@@ -103,6 +107,14 @@ const messages = defineMessages({ | |||
103 | id: 'settings.app.form.searchEngine', | 107 | id: 'settings.app.form.searchEngine', |
104 | defaultMessage: 'Search engine', | 108 | defaultMessage: 'Search engine', |
105 | }, | 109 | }, |
110 | translatorEngine: { | ||
111 | id: 'settings.app.form.translatorEngine', | ||
112 | defaultMessage: 'Translator Engine', | ||
113 | }, | ||
114 | translatorLanguage: { | ||
115 | id: 'settings.app.form.translatorLanguage', | ||
116 | defaultMessage: 'Default Translator language', | ||
117 | }, | ||
106 | hibernateOnStartup: { | 118 | hibernateOnStartup: { |
107 | id: 'settings.app.form.hibernateOnStartup', | 119 | id: 'settings.app.form.hibernateOnStartup', |
108 | defaultMessage: 'Keep services in hibernation on startup', | 120 | defaultMessage: 'Keep services in hibernation on startup', |
@@ -267,6 +279,10 @@ const messages = defineMessages({ | |||
267 | id: 'settings.app.form.enableSpellchecking', | 279 | id: 'settings.app.form.enableSpellchecking', |
268 | defaultMessage: 'Enable spell checking', | 280 | defaultMessage: 'Enable spell checking', |
269 | }, | 281 | }, |
282 | enableTranslator: { | ||
283 | id: 'settings.app.form.enableTranslator', | ||
284 | defaultMessage: 'Enable Translator', | ||
285 | }, | ||
270 | enableGPUAcceleration: { | 286 | enableGPUAcceleration: { |
271 | id: 'settings.app.form.enableGPUAcceleration', | 287 | id: 'settings.app.form.enableGPUAcceleration', |
272 | defaultMessage: 'Enable GPU Acceleration', | 288 | defaultMessage: 'Enable GPU Acceleration', |
@@ -342,6 +358,8 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
342 | notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage), | 358 | notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage), |
343 | navigationBarBehaviour: settingsData.navigationBarBehaviour, | 359 | navigationBarBehaviour: settingsData.navigationBarBehaviour, |
344 | searchEngine: settingsData.searchEngine, | 360 | searchEngine: settingsData.searchEngine, |
361 | translatorEngine: settingsData.translatorEngine, | ||
362 | translatorLanguage: settingsData.translatorLanguage, | ||
345 | hibernateOnStartup: Boolean(settingsData.hibernateOnStartup), | 363 | hibernateOnStartup: Boolean(settingsData.hibernateOnStartup), |
346 | hibernationStrategy: Number(settingsData.hibernationStrategy), | 364 | hibernationStrategy: Number(settingsData.hibernationStrategy), |
347 | wakeUpStrategy: Number(settingsData.wakeUpStrategy), | 365 | wakeUpStrategy: Number(settingsData.wakeUpStrategy), |
@@ -394,6 +412,7 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
394 | ), | 412 | ), |
395 | showDragArea: Boolean(settingsData.showDragArea), | 413 | showDragArea: Boolean(settingsData.showDragArea), |
396 | enableSpellchecking: Boolean(settingsData.enableSpellchecking), | 414 | enableSpellchecking: Boolean(settingsData.enableSpellchecking), |
415 | enableTranslator: Boolean(settingsData.enableTranslator), | ||
397 | spellcheckerLanguage: settingsData.spellcheckerLanguage, | 416 | spellcheckerLanguage: settingsData.spellcheckerLanguage, |
398 | userAgentPref: settingsData.userAgentPref, | 417 | userAgentPref: settingsData.userAgentPref, |
399 | beta: Boolean(settingsData.beta), // we need this info in the main process as well | 418 | beta: Boolean(settingsData.beta), // we need this info in the main process as well |
@@ -451,6 +470,16 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
451 | sort: false, | 470 | sort: false, |
452 | }); | 471 | }); |
453 | 472 | ||
473 | const translatorEngines = getSelectOptions({ | ||
474 | locales: TRANSLATOR_ENGINE_NAMES, | ||
475 | sort: false, | ||
476 | }); | ||
477 | |||
478 | const translatorLanguages = getSelectOptions({ | ||
479 | locales: LIBRETRANSLATE_TRANSLATOR_LANGUAGES, | ||
480 | sort: false, | ||
481 | }); | ||
482 | |||
454 | const hibernationStrategies = getSelectOptions({ | 483 | const hibernationStrategies = getSelectOptions({ |
455 | locales: HIBERNATION_STRATEGIES, | 484 | locales: HIBERNATION_STRATEGIES, |
456 | sort: false, | 485 | sort: false, |
@@ -574,6 +603,18 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
574 | default: DEFAULT_APP_SETTINGS.searchEngine, | 603 | default: DEFAULT_APP_SETTINGS.searchEngine, |
575 | options: searchEngines, | 604 | options: searchEngines, |
576 | }, | 605 | }, |
606 | translatorEngine: { | ||
607 | label: intl.formatMessage(messages.translatorEngine), | ||
608 | value: settings.all.app.translatorEngine, | ||
609 | default: DEFAULT_APP_SETTINGS.translatorEngine, | ||
610 | options: translatorEngines, | ||
611 | }, | ||
612 | translatorLanguage: { | ||
613 | label: intl.formatMessage(messages.translatorLanguage), | ||
614 | value: settings.all.app.translatorLanguage, | ||
615 | default: DEFAULT_APP_SETTINGS.translatorLanguage, | ||
616 | options: translatorLanguages, | ||
617 | }, | ||
577 | hibernateOnStartup: { | 618 | hibernateOnStartup: { |
578 | label: intl.formatMessage(messages.hibernateOnStartup), | 619 | label: intl.formatMessage(messages.hibernateOnStartup), |
579 | value: settings.all.app.hibernateOnStartup, | 620 | value: settings.all.app.hibernateOnStartup, |
@@ -677,6 +718,11 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
677 | value: settings.all.app.enableSpellchecking, | 718 | value: settings.all.app.enableSpellchecking, |
678 | default: DEFAULT_APP_SETTINGS.enableSpellchecking, | 719 | default: DEFAULT_APP_SETTINGS.enableSpellchecking, |
679 | }, | 720 | }, |
721 | enableTranslator: { | ||
722 | label: intl.formatMessage(messages.enableTranslator), | ||
723 | value: settings.all.app.enableTranslator, | ||
724 | default: DEFAULT_APP_SETTINGS.enableTranslator, | ||
725 | }, | ||
680 | spellcheckerLanguage: { | 726 | spellcheckerLanguage: { |
681 | label: intl.formatMessage(globalMessages.spellcheckerLanguage), | 727 | label: intl.formatMessage(globalMessages.spellcheckerLanguage), |
682 | value: settings.all.app.spellcheckerLanguage, | 728 | value: settings.all.app.spellcheckerLanguage, |
@@ -828,6 +874,14 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
828 | }, | 874 | }, |
829 | }; | 875 | }; |
830 | 876 | ||
877 | if (settings.app.translatorEngine === TRANSLATOR_ENGINE_GOOGLE) { | ||
878 | const translatorGoogleLanguages = getSelectOptions({ | ||
879 | locales: GOOGLE_TRANSLATOR_LANGUAGES, | ||
880 | sort: false, | ||
881 | }); | ||
882 | config.fields.translatorLanguage.options = translatorGoogleLanguages; | ||
883 | } | ||
884 | |||
831 | if (workspaces.isFeatureActive) { | 885 | if (workspaces.isFeatureActive) { |
832 | config.fields.keepAllWorkspacesLoaded = { | 886 | config.fields.keepAllWorkspacesLoaded = { |
833 | label: intl.formatMessage(messages.keepAllWorkspacesLoaded), | 887 | label: intl.formatMessage(messages.keepAllWorkspacesLoaded), |
diff --git a/src/helpers/translation-helpers.ts b/src/helpers/translation-helpers.ts new file mode 100644 index 000000000..215b2a49c --- /dev/null +++ b/src/helpers/translation-helpers.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import fetch from 'node-fetch'; | ||
2 | import translateGoogle from 'translate-google'; | ||
3 | import { LIVE_API_FERDIUM_LIBRETRANSLATE } from '../config'; | ||
4 | |||
5 | export async function translateTo( | ||
6 | text: string, | ||
7 | translateToLanguage: string, | ||
8 | translatorEngine: string, | ||
9 | ): Promise<{ text: string; error: boolean }> { | ||
10 | const errorText = | ||
11 | // TODO: Need to support i18n | ||
12 | 'FERDIUM ERROR: An error occured. Please select less text to translate or try again later.'; | ||
13 | |||
14 | if (translatorEngine === 'Google') { | ||
15 | try { | ||
16 | const res = await translateGoogle(text, { | ||
17 | to: translateToLanguage, | ||
18 | }); | ||
19 | |||
20 | return { text: res, error: false }; | ||
21 | } catch { | ||
22 | return { text: errorText, error: true }; | ||
23 | } | ||
24 | } else if (translatorEngine === 'LibreTranslate') { | ||
25 | try { | ||
26 | const res = await fetch(LIVE_API_FERDIUM_LIBRETRANSLATE, { | ||
27 | method: 'POST', | ||
28 | body: JSON.stringify({ | ||
29 | q: text, | ||
30 | source: 'auto', | ||
31 | target: translateToLanguage, | ||
32 | }), | ||
33 | headers: { | ||
34 | 'Content-Type': 'application/json', | ||
35 | }, | ||
36 | }); | ||
37 | |||
38 | const response = await res.json(); | ||
39 | |||
40 | return { text: response.translatedText, error: false }; | ||
41 | } catch { | ||
42 | return { text: errorText, error: true }; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | return { text: errorText, error: true }; | ||
47 | } | ||
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index a98bc2f77..4dc8bad82 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -219,6 +219,7 @@ | |||
219 | "settings.app.form.enableSpellchecking": "Enable spell checking", | 219 | "settings.app.form.enableSpellchecking": "Enable spell checking", |
220 | "settings.app.form.enableSystemTray": "Always show Ferdium in System Tray", | 220 | "settings.app.form.enableSystemTray": "Always show Ferdium in System Tray", |
221 | "settings.app.form.enableTodos": "Enable Ferdium Todos", | 221 | "settings.app.form.enableTodos": "Enable Ferdium Todos", |
222 | "settings.app.form.enableTranslator": "Enable Translator", | ||
222 | "settings.app.form.grayscaleServicesDim": "Grayscale dim level", | 223 | "settings.app.form.grayscaleServicesDim": "Grayscale dim level", |
223 | "settings.app.form.hibernateOnStartup": "Keep services in hibernation on startup", | 224 | "settings.app.form.hibernateOnStartup": "Keep services in hibernation on startup", |
224 | "settings.app.form.hibernationStrategy": "Hibernation strategy", | 225 | "settings.app.form.hibernationStrategy": "Hibernation strategy", |
@@ -256,6 +257,8 @@ | |||
256 | "settings.app.form.splitColumns": "Number of columns", | 257 | "settings.app.form.splitColumns": "Number of columns", |
257 | "settings.app.form.splitMode": "Enable Split View Mode", | 258 | "settings.app.form.splitMode": "Enable Split View Mode", |
258 | "settings.app.form.startMinimized": "Start minimized", | 259 | "settings.app.form.startMinimized": "Start minimized", |
260 | "settings.app.form.translatorEngine": "Translator Engine", | ||
261 | "settings.app.form.translatorLanguage": "Default Translator language", | ||
259 | "settings.app.form.universalDarkMode": "Enable universal Dark Mode", | 262 | "settings.app.form.universalDarkMode": "Enable universal Dark Mode", |
260 | "settings.app.form.useGrayscaleServices": "Use grayscale services", | 263 | "settings.app.form.useGrayscaleServices": "Use grayscale services", |
261 | "settings.app.form.useHorizontalStyle": "Use horizontal style", | 264 | "settings.app.form.useHorizontalStyle": "Use horizontal style", |
diff --git a/src/index.ts b/src/index.ts index 05e222a25..7c80ca955 100644 --- a/src/index.ts +++ b/src/index.ts | |||
@@ -45,6 +45,7 @@ import './electron/exception'; | |||
45 | import { asarPath } from './helpers/asar-helpers'; | 45 | import { asarPath } from './helpers/asar-helpers'; |
46 | import { openExternalUrl } from './helpers/url-helpers'; | 46 | import { openExternalUrl } from './helpers/url-helpers'; |
47 | import userAgent from './helpers/userAgent-helpers'; | 47 | import userAgent from './helpers/userAgent-helpers'; |
48 | import { translateTo } from './helpers/translation-helpers'; | ||
48 | 49 | ||
49 | const debug = require('./preload-safe-debug')('Ferdium:App'); | 50 | const debug = require('./preload-safe-debug')('Ferdium:App'); |
50 | 51 | ||
@@ -500,6 +501,18 @@ app.on('login', (event, _webContents, _request, authInfo, callback) => { | |||
500 | } | 501 | } |
501 | }); | 502 | }); |
502 | 503 | ||
504 | ipcMain.handle( | ||
505 | 'translate', | ||
506 | async (_e, { text, translateToLanguage, translatorEngine }) => { | ||
507 | const response = await translateTo( | ||
508 | text, | ||
509 | translateToLanguage, | ||
510 | translatorEngine, | ||
511 | ); | ||
512 | return response; | ||
513 | }, | ||
514 | ); | ||
515 | |||
503 | // TODO: evaluate if we need to store the authCallback for every service | 516 | // TODO: evaluate if we need to store the authCallback for every service |
504 | ipcMain.on('feature-basic-auth-credentials', (_e, { user, password }) => { | 517 | ipcMain.on('feature-basic-auth-credentials', (_e, { user, password }) => { |
505 | debug('Received basic auth credentials', user, '********'); | 518 | debug('Received basic auth credentials', user, '********'); |
diff --git a/src/models/IContextMenuParams.ts b/src/models/IContextMenuParams.ts new file mode 100644 index 000000000..c661b4595 --- /dev/null +++ b/src/models/IContextMenuParams.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | export default interface IContextMenuParams extends Electron.ContextMenuParams { | ||
2 | enableTranslator: boolean; | ||
3 | clipboardNotifications: boolean; | ||
4 | searchEngine: string; | ||
5 | translatorEngine: string; | ||
6 | translatorLanguage: string; | ||
7 | } | ||
diff --git a/src/stores/ServicesStore.ts b/src/stores/ServicesStore.ts index 83ec7d18e..00cf33b17 100644 --- a/src/stores/ServicesStore.ts +++ b/src/stores/ServicesStore.ts | |||
@@ -158,6 +158,13 @@ export default class ServicesStore extends TypedStore { | |||
158 | ); | 158 | ); |
159 | 159 | ||
160 | reaction( | 160 | reaction( |
161 | () => this.stores.settings.app.enableTranslator, | ||
162 | () => { | ||
163 | this._shareSettingsWithServiceProcess(); | ||
164 | }, | ||
165 | ); | ||
166 | |||
167 | reaction( | ||
161 | () => this.stores.settings.app.spellcheckerLanguage, | 168 | () => this.stores.settings.app.spellcheckerLanguage, |
162 | () => { | 169 | () => { |
163 | this._shareSettingsWithServiceProcess(); | 170 | this._shareSettingsWithServiceProcess(); |
@@ -207,6 +214,20 @@ export default class ServicesStore extends TypedStore { | |||
207 | ); | 214 | ); |
208 | 215 | ||
209 | reaction( | 216 | reaction( |
217 | () => this.stores.settings.app.translatorEngine, | ||
218 | () => { | ||
219 | this._shareSettingsWithServiceProcess(); | ||
220 | }, | ||
221 | ); | ||
222 | |||
223 | reaction( | ||
224 | () => this.stores.settings.app.translatorLanguage, | ||
225 | () => { | ||
226 | this._shareSettingsWithServiceProcess(); | ||
227 | }, | ||
228 | ); | ||
229 | |||
230 | reaction( | ||
210 | () => this.stores.settings.app.clipboardNotifications, | 231 | () => this.stores.settings.app.clipboardNotifications, |
211 | () => { | 232 | () => { |
212 | this._shareSettingsWithServiceProcess(); | 233 | this._shareSettingsWithServiceProcess(); |
diff --git a/src/webview/contextMenu.ts b/src/webview/contextMenu.ts index 72f927ef4..61771854f 100644 --- a/src/webview/contextMenu.ts +++ b/src/webview/contextMenu.ts | |||
@@ -9,6 +9,9 @@ export default async function setupContextMenu( | |||
9 | getSpellcheckerLanguage: () => void, | 9 | getSpellcheckerLanguage: () => void, |
10 | getSearchEngine: () => void, | 10 | getSearchEngine: () => void, |
11 | getClipboardNotifications: () => void, | 11 | getClipboardNotifications: () => void, |
12 | getEnableTranslator: () => void, | ||
13 | getTranslatorEngine: () => void, | ||
14 | getTranslatorLanguage: () => void, | ||
12 | ) { | 15 | ) { |
13 | const contextMenuBuilder = new ContextMenuBuilder(webContents); | 16 | const contextMenuBuilder = new ContextMenuBuilder(webContents); |
14 | 17 | ||
@@ -19,6 +22,9 @@ export default async function setupContextMenu( | |||
19 | ...props, | 22 | ...props, |
20 | searchEngine: getSearchEngine(), | 23 | searchEngine: getSearchEngine(), |
21 | clipboardNotifications: getClipboardNotifications(), | 24 | clipboardNotifications: getClipboardNotifications(), |
25 | enableTranslator: getEnableTranslator(), | ||
26 | translatorEngine: getTranslatorEngine(), | ||
27 | translatorLanguage: getTranslatorLanguage(), | ||
22 | }, | 28 | }, |
23 | // @ts-expect-error Expected 1 arguments, but got 4. | 29 | // @ts-expect-error Expected 1 arguments, but got 4. |
24 | isSpellcheckEnabled(), | 30 | isSpellcheckEnabled(), |
diff --git a/src/webview/contextMenuBuilder.ts b/src/webview/contextMenuBuilder.ts index 7bd86556e..2e64977c1 100644 --- a/src/webview/contextMenuBuilder.ts +++ b/src/webview/contextMenuBuilder.ts | |||
@@ -11,8 +11,16 @@ import { clipboard, ipcRenderer, nativeImage, WebContents } from 'electron'; | |||
11 | import { Menu, MenuItem } from '@electron/remote'; | 11 | import { Menu, MenuItem } from '@electron/remote'; |
12 | import { cmdOrCtrlShortcutKey, isMac } from '../environment'; | 12 | import { cmdOrCtrlShortcutKey, isMac } from '../environment'; |
13 | 13 | ||
14 | import { SEARCH_ENGINE_NAMES, SEARCH_ENGINE_URLS } from '../config'; | 14 | import { |
15 | SEARCH_ENGINE_NAMES, | ||
16 | SEARCH_ENGINE_URLS, | ||
17 | GOOGLE_TRANSLATOR_LANGUAGES, | ||
18 | TRANSLATOR_ENGINE_GOOGLE, | ||
19 | TRANSLATOR_ENGINE_LIBRETRANSLATE, | ||
20 | LIBRETRANSLATE_TRANSLATOR_LANGUAGES, | ||
21 | } from '../config'; | ||
15 | import { openExternalUrl } from '../helpers/url-helpers'; | 22 | import { openExternalUrl } from '../helpers/url-helpers'; |
23 | import IContextMenuParams from '../models/IContextMenuParams'; | ||
16 | 24 | ||
17 | function matchesWord(string: string) { | 25 | function matchesWord(string: string) { |
18 | const regex = | 26 | const regex = |
@@ -21,6 +29,83 @@ function matchesWord(string: string) { | |||
21 | return string.match(regex); | 29 | return string.match(regex); |
22 | } | 30 | } |
23 | 31 | ||
32 | function childOf(node, ancestor) { | ||
33 | let child = node; | ||
34 | while (child !== null) { | ||
35 | if (child === ancestor) return true; | ||
36 | child = child.parentNode; | ||
37 | } | ||
38 | return false; | ||
39 | } | ||
40 | |||
41 | function translatePopup(res, isError: boolean = false) { | ||
42 | const elementExists = document.querySelector('#container-ferdium-translator'); | ||
43 | if (elementExists) { | ||
44 | elementExists.remove(); | ||
45 | } | ||
46 | |||
47 | const style = document.createElement('style'); | ||
48 | style.innerHTML = ` | ||
49 | .container-ferdium-translator { | ||
50 | position: fixed; | ||
51 | opacity: 0.9; | ||
52 | z-index: 999999; | ||
53 | ${ | ||
54 | isError | ||
55 | ? `background: rgb(255 37 37);` | ||
56 | : `background: rgb(131 131 131);` | ||
57 | } | ||
58 | border-radius: 8px; | ||
59 | top: 5%; | ||
60 | left: 50%; | ||
61 | transform: translate(-50%, -5%); | ||
62 | display: flex; | ||
63 | flex-direction: row; | ||
64 | -webkit-box-shadow: 0px 10px 13px -7px #000000, 5px 5px 13px 9px rgba(0,0,0,0); | ||
65 | overflow: auto; | ||
66 | max-height: 95%; | ||
67 | max-width: 90%; | ||
68 | width: max-content; | ||
69 | height: max-content; | ||
70 | -webkit-animation: scale-up-center 0.4s cubic-bezier(0.390, 0.575, 0.565, 1.000) both; | ||
71 | } | ||
72 | .container-ferdium-translator > p { | ||
73 | color: white; | ||
74 | margin: 10px; | ||
75 | text-align: justify; | ||
76 | } | ||
77 | |||
78 | @-webkit-keyframes scale-up-center { | ||
79 | 0% { | ||
80 | -webkit-transform: translate(-50%, -50%) scale(0.5); | ||
81 | } | ||
82 | 100% { | ||
83 | -webkit-transform: translate(-50%, -50%) scale(1); | ||
84 | } | ||
85 | } | ||
86 | `; | ||
87 | document.head.append(style); | ||
88 | |||
89 | const para = document.createElement('p'); | ||
90 | |||
91 | const node = document.createTextNode(res); | ||
92 | para.append(node); | ||
93 | |||
94 | const div = document.createElement('div'); | ||
95 | div.setAttribute('id', 'container-ferdium-translator'); | ||
96 | div.setAttribute('class', 'container-ferdium-translator'); | ||
97 | |||
98 | div.append(para); | ||
99 | |||
100 | document.body.insertBefore(div, document.body.firstChild); | ||
101 | |||
102 | document.addEventListener('click', e => { | ||
103 | if (div !== e.target && !childOf(e.target, div)) { | ||
104 | div?.remove(); | ||
105 | } | ||
106 | }); | ||
107 | } | ||
108 | |||
24 | interface ContextMenuStringTable { | 109 | interface ContextMenuStringTable { |
25 | lookUpDefinition: ({ word }: { word: string }) => string; | 110 | lookUpDefinition: ({ word }: { word: string }) => string; |
26 | cut: () => string; | 111 | cut: () => string; |
@@ -28,6 +113,17 @@ interface ContextMenuStringTable { | |||
28 | paste: () => string; | 113 | paste: () => string; |
29 | pasteAndMatchStyle: () => string; | 114 | pasteAndMatchStyle: () => string; |
30 | searchWith: ({ searchEngine }: { searchEngine: string }) => string; | 115 | searchWith: ({ searchEngine }: { searchEngine: string }) => string; |
116 | translate: () => string; | ||
117 | quickTranslate: ({ | ||
118 | translatorLanguage, | ||
119 | }: { | ||
120 | translatorLanguage: string; | ||
121 | }) => string; | ||
122 | translateLanguage: ({ | ||
123 | translatorLanguage, | ||
124 | }: { | ||
125 | translatorLanguage: string; | ||
126 | }) => string; | ||
31 | openLinkUrl: () => string; | 127 | openLinkUrl: () => string; |
32 | openLinkInFerdiumUrl: () => string; | 128 | openLinkInFerdiumUrl: () => string; |
33 | openInBrowser: () => string; | 129 | openInBrowser: () => string; |
@@ -52,6 +148,10 @@ const contextMenuStringTable: ContextMenuStringTable = { | |||
52 | paste: () => 'Paste', | 148 | paste: () => 'Paste', |
53 | pasteAndMatchStyle: () => 'Paste and match style', | 149 | pasteAndMatchStyle: () => 'Paste and match style', |
54 | searchWith: ({ searchEngine }) => `Search with ${searchEngine}`, | 150 | searchWith: ({ searchEngine }) => `Search with ${searchEngine}`, |
151 | translate: () => `Translate to ...`, | ||
152 | quickTranslate: ({ translatorLanguage }) => | ||
153 | `Translate to ${translatorLanguage}`, | ||
154 | translateLanguage: ({ translatorLanguage }) => `${translatorLanguage}`, | ||
55 | openLinkUrl: () => 'Open Link', | 155 | openLinkUrl: () => 'Open Link', |
56 | openLinkInFerdiumUrl: () => 'Open Link in Ferdium', | 156 | openLinkInFerdiumUrl: () => 'Open Link in Ferdium', |
57 | openInBrowser: () => 'Open in Browser', | 157 | openInBrowser: () => 'Open in Browser', |
@@ -127,7 +227,7 @@ export class ContextMenuBuilder { | |||
127 | * | 227 | * |
128 | * @param contextInfo The object returned from the 'context-menu' Electron event. | 228 | * @param contextInfo The object returned from the 'context-menu' Electron event. |
129 | */ | 229 | */ |
130 | async showPopupMenu(contextInfo: Electron.ContextMenuParams): Promise<void> { | 230 | async showPopupMenu(contextInfo: IContextMenuParams): Promise<void> { |
131 | const menu = await this.buildMenuForElement(contextInfo); | 231 | const menu = await this.buildMenuForElement(contextInfo); |
132 | if (!menu) return; | 232 | if (!menu) return; |
133 | menu.popup(); | 233 | menu.popup(); |
@@ -139,7 +239,7 @@ export class ContextMenuBuilder { | |||
139 | * the list but use most of the default behavior. | 239 | * the list but use most of the default behavior. |
140 | */ | 240 | */ |
141 | async buildMenuForElement( | 241 | async buildMenuForElement( |
142 | info: Electron.ContextMenuParams, | 242 | info: IContextMenuParams, |
143 | ): Promise<Electron.CrossProcessExports.Menu> { | 243 | ): Promise<Electron.CrossProcessExports.Menu> { |
144 | if (info.linkURL && info.linkURL.length > 0) { | 244 | if (info.linkURL && info.linkURL.length > 0) { |
145 | return this.buildMenuForLink(info); | 245 | return this.buildMenuForLink(info); |
@@ -165,12 +265,17 @@ export class ContextMenuBuilder { | |||
165 | * @return {Menu} The `Menu` | 265 | * @return {Menu} The `Menu` |
166 | */ | 266 | */ |
167 | buildMenuForTextInput( | 267 | buildMenuForTextInput( |
168 | menuInfo: Electron.ContextMenuParams, | 268 | menuInfo: IContextMenuParams, |
169 | ): Electron.CrossProcessExports.Menu { | 269 | ): Electron.CrossProcessExports.Menu { |
170 | const menu = new Menu(); | 270 | const menu = new Menu(); |
171 | 271 | ||
272 | const { enableTranslator } = menuInfo; | ||
273 | |||
172 | this.addSpellingItems(menu, menuInfo); | 274 | this.addSpellingItems(menu, menuInfo); |
173 | this.addSearchItems(menu, menuInfo); | 275 | this.addSearchItems(menu, menuInfo); |
276 | if (enableTranslator) { | ||
277 | this.addTranslateItems(menu, menuInfo); | ||
278 | } | ||
174 | 279 | ||
175 | this.addCut(menu, menuInfo); | 280 | this.addCut(menu, menuInfo); |
176 | this.addCopy(menu, menuInfo); | 281 | this.addCopy(menu, menuInfo); |
@@ -194,7 +299,7 @@ export class ContextMenuBuilder { | |||
194 | * @return {Menu} The `Menu` | 299 | * @return {Menu} The `Menu` |
195 | */ | 300 | */ |
196 | buildMenuForLink( | 301 | buildMenuForLink( |
197 | menuInfo: Electron.ContextMenuParams, | 302 | menuInfo: IContextMenuParams, |
198 | ): Electron.CrossProcessExports.Menu { | 303 | ): Electron.CrossProcessExports.Menu { |
199 | const menu = new Menu(); | 304 | const menu = new Menu(); |
200 | const isEmailAddress = menuInfo.linkURL.startsWith('mailto:'); | 305 | const isEmailAddress = menuInfo.linkURL.startsWith('mailto:'); |
@@ -208,7 +313,6 @@ export class ContextMenuBuilder { | |||
208 | const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL; | 313 | const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL; |
209 | clipboard.writeText(url); | 314 | clipboard.writeText(url); |
210 | this._sendNotificationOnClipboardEvent( | 315 | this._sendNotificationOnClipboardEvent( |
211 | // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. | ||
212 | menuInfo.clipboardNotifications, | 316 | menuInfo.clipboardNotifications, |
213 | () => `Link URL copied: ${url}`, | 317 | () => `Link URL copied: ${url}`, |
214 | ); | 318 | ); |
@@ -257,11 +361,16 @@ export class ContextMenuBuilder { | |||
257 | * Builds a menu applicable to a text field. | 361 | * Builds a menu applicable to a text field. |
258 | */ | 362 | */ |
259 | buildMenuForText( | 363 | buildMenuForText( |
260 | menuInfo: Electron.ContextMenuParams, | 364 | menuInfo: IContextMenuParams, |
261 | ): Electron.CrossProcessExports.Menu { | 365 | ): Electron.CrossProcessExports.Menu { |
262 | const menu = new Menu(); | 366 | const menu = new Menu(); |
263 | 367 | ||
368 | const { enableTranslator } = menuInfo; | ||
369 | |||
264 | this.addSearchItems(menu, menuInfo); | 370 | this.addSearchItems(menu, menuInfo); |
371 | if (enableTranslator) { | ||
372 | this.addTranslateItems(menu, menuInfo); | ||
373 | } | ||
265 | this.addCopy(menu, menuInfo); | 374 | this.addCopy(menu, menuInfo); |
266 | this.addInspectElement(menu, menuInfo); | 375 | this.addInspectElement(menu, menuInfo); |
267 | // @ts-expect-error Expected 1 arguments, but got 2. | 376 | // @ts-expect-error Expected 1 arguments, but got 2. |
@@ -283,7 +392,7 @@ export class ContextMenuBuilder { | |||
283 | * @return {Menu} The `Menu` | 392 | * @return {Menu} The `Menu` |
284 | */ | 393 | */ |
285 | buildMenuForImage( | 394 | buildMenuForImage( |
286 | menuInfo: Electron.ContextMenuParams, | 395 | menuInfo: IContextMenuParams, |
287 | ): Electron.CrossProcessExports.Menu { | 396 | ): Electron.CrossProcessExports.Menu { |
288 | const menu = new Menu(); | 397 | const menu = new Menu(); |
289 | 398 | ||
@@ -303,7 +412,7 @@ export class ContextMenuBuilder { | |||
303 | */ | 412 | */ |
304 | addSpellingItems( | 413 | addSpellingItems( |
305 | menu: Electron.CrossProcessExports.Menu, | 414 | menu: Electron.CrossProcessExports.Menu, |
306 | menuInfo: Electron.ContextMenuParams, | 415 | menuInfo: IContextMenuParams, |
307 | ) { | 416 | ) { |
308 | const webContents = this.getWebContents(); | 417 | const webContents = this.getWebContents(); |
309 | // Add each spelling suggestion | 418 | // Add each spelling suggestion |
@@ -338,7 +447,7 @@ export class ContextMenuBuilder { | |||
338 | */ | 447 | */ |
339 | addSearchItems( | 448 | addSearchItems( |
340 | menu: Electron.CrossProcessExports.Menu, | 449 | menu: Electron.CrossProcessExports.Menu, |
341 | menuInfo: Electron.ContextMenuParams, | 450 | menuInfo: IContextMenuParams, |
342 | ) { | 451 | ) { |
343 | if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) { | 452 | if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) { |
344 | return menu; | 453 | return menu; |
@@ -364,11 +473,9 @@ export class ContextMenuBuilder { | |||
364 | 473 | ||
365 | const search = new MenuItem({ | 474 | const search = new MenuItem({ |
366 | label: this.stringTable.searchWith({ | 475 | label: this.stringTable.searchWith({ |
367 | // @ts-expect-error Property 'searchEngine' does not exist on type 'ContextMenuParams'. | ||
368 | searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine], | 476 | searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine], |
369 | }), | 477 | }), |
370 | click: () => { | 478 | click: () => { |
371 | // @ts-expect-error Property 'searchEngine' does not exist on type 'ContextMenuParams'. | ||
372 | const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({ | 479 | const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({ |
373 | searchTerm: encodeURIComponent(menuInfo.selectionText), | 480 | searchTerm: encodeURIComponent(menuInfo.selectionText), |
374 | }); | 481 | }); |
@@ -382,7 +489,106 @@ export class ContextMenuBuilder { | |||
382 | return menu; | 489 | return menu; |
383 | } | 490 | } |
384 | 491 | ||
385 | isSrcUrlValid(menuInfo: Electron.ContextMenuParams) { | 492 | /** |
493 | * Adds translate-related menu items. | ||
494 | */ | ||
495 | addTranslateItems( | ||
496 | menu: Electron.CrossProcessExports.Menu, | ||
497 | menuInfo: IContextMenuParams, | ||
498 | ) { | ||
499 | const { translatorEngine } = menuInfo; | ||
500 | |||
501 | if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) { | ||
502 | return menu; | ||
503 | } | ||
504 | |||
505 | const match = matchesWord(menuInfo.selectionText); | ||
506 | if (!match || match.length === 0) { | ||
507 | return menu; | ||
508 | } | ||
509 | |||
510 | const generateTranslationItems = async ( | ||
511 | translateToLanguage: string, | ||
512 | translatorEngine: string, | ||
513 | ) => { | ||
514 | // TODO: Need to support i18n | ||
515 | translatePopup('Loading...', false); | ||
516 | |||
517 | const translatedText = await ipcRenderer.invoke('translate', { | ||
518 | text: menuInfo.selectionText, | ||
519 | translateToLanguage, | ||
520 | translatorEngine, | ||
521 | }); | ||
522 | |||
523 | translatePopup(translatedText.text, translatedText.error); | ||
524 | }; | ||
525 | |||
526 | let arrayLanguagesAfterEngine; | ||
527 | if (translatorEngine === TRANSLATOR_ENGINE_LIBRETRANSLATE) { | ||
528 | arrayLanguagesAfterEngine = LIBRETRANSLATE_TRANSLATOR_LANGUAGES; | ||
529 | } else if (translatorEngine === TRANSLATOR_ENGINE_GOOGLE) { | ||
530 | arrayLanguagesAfterEngine = GOOGLE_TRANSLATOR_LANGUAGES; | ||
531 | } | ||
532 | |||
533 | const arrayLanguages = Object.keys(arrayLanguagesAfterEngine).map( | ||
534 | code => arrayLanguagesAfterEngine[code], | ||
535 | ); | ||
536 | |||
537 | const menuLanguages = new Menu(); | ||
538 | |||
539 | const getLanguageCode = (obj, val): string => | ||
540 | Object.keys(obj).find(key => obj[key] === val)!; | ||
541 | |||
542 | for (const language in arrayLanguages) { | ||
543 | if (arrayLanguages[language]) { | ||
544 | const languageItem = new MenuItem({ | ||
545 | label: this.stringTable.translateLanguage({ | ||
546 | translatorLanguage: arrayLanguages[language], | ||
547 | }), | ||
548 | click: () => { | ||
549 | const translateToLanguageCode = getLanguageCode( | ||
550 | arrayLanguagesAfterEngine, | ||
551 | arrayLanguages[language], | ||
552 | ); | ||
553 | generateTranslationItems(translateToLanguageCode, translatorEngine); | ||
554 | }, | ||
555 | }); | ||
556 | menuLanguages.append(languageItem); | ||
557 | } | ||
558 | } | ||
559 | |||
560 | const translateToLanguage = | ||
561 | arrayLanguagesAfterEngine[menuInfo.translatorLanguage]; | ||
562 | |||
563 | const translateToLanguageCode = getLanguageCode( | ||
564 | arrayLanguagesAfterEngine, | ||
565 | translateToLanguage, | ||
566 | ); | ||
567 | const quickTranslateItem = new MenuItem({ | ||
568 | label: this.stringTable.quickTranslate({ | ||
569 | translatorLanguage: translateToLanguage, | ||
570 | }), | ||
571 | click: () => { | ||
572 | generateTranslationItems(translateToLanguageCode, translatorEngine); | ||
573 | }, | ||
574 | }); | ||
575 | |||
576 | const translateItem = new MenuItem({ | ||
577 | label: this.stringTable.translate(), | ||
578 | submenu: menuLanguages, | ||
579 | click: () => { | ||
580 | generateTranslationItems(translateToLanguageCode, translatorEngine); | ||
581 | }, | ||
582 | }); | ||
583 | |||
584 | menu.append(translateItem); | ||
585 | menu.append(quickTranslateItem); | ||
586 | this.addSeparator(menu); | ||
587 | |||
588 | return menu; | ||
589 | } | ||
590 | |||
591 | isSrcUrlValid(menuInfo: IContextMenuParams) { | ||
386 | return menuInfo.srcURL && menuInfo.srcURL.length > 0; | 592 | return menuInfo.srcURL && menuInfo.srcURL.length > 0; |
387 | } | 593 | } |
388 | 594 | ||
@@ -391,7 +597,7 @@ export class ContextMenuBuilder { | |||
391 | */ | 597 | */ |
392 | addImageItems( | 598 | addImageItems( |
393 | menu: Electron.CrossProcessExports.Menu, | 599 | menu: Electron.CrossProcessExports.Menu, |
394 | menuInfo: Electron.ContextMenuParams, | 600 | menuInfo: IContextMenuParams, |
395 | ) { | 601 | ) { |
396 | const copyImage = new MenuItem({ | 602 | const copyImage = new MenuItem({ |
397 | label: this.stringTable.copyImage(), | 603 | label: this.stringTable.copyImage(), |
@@ -403,7 +609,6 @@ export class ContextMenuBuilder { | |||
403 | ); | 609 | ); |
404 | 610 | ||
405 | this._sendNotificationOnClipboardEvent( | 611 | this._sendNotificationOnClipboardEvent( |
406 | // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. | ||
407 | menuInfo.clipboardNotifications, | 612 | menuInfo.clipboardNotifications, |
408 | () => `Image copied from URL: ${menuInfo.srcURL}`, | 613 | () => `Image copied from URL: ${menuInfo.srcURL}`, |
409 | ); | 614 | ); |
@@ -418,7 +623,6 @@ export class ContextMenuBuilder { | |||
418 | click: () => { | 623 | click: () => { |
419 | const result = clipboard.writeText(menuInfo.srcURL); | 624 | const result = clipboard.writeText(menuInfo.srcURL); |
420 | this._sendNotificationOnClipboardEvent( | 625 | this._sendNotificationOnClipboardEvent( |
421 | // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. | ||
422 | menuInfo.clipboardNotifications, | 626 | menuInfo.clipboardNotifications, |
423 | () => `Image URL copied: ${menuInfo.srcURL}`, | 627 | () => `Image URL copied: ${menuInfo.srcURL}`, |
424 | ); | 628 | ); |
@@ -446,7 +650,6 @@ export class ContextMenuBuilder { | |||
446 | }); | 650 | }); |
447 | }); | 651 | }); |
448 | this._sendNotificationOnClipboardEvent( | 652 | this._sendNotificationOnClipboardEvent( |
449 | // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. | ||
450 | menuInfo.clipboardNotifications, | 653 | menuInfo.clipboardNotifications, |
451 | () => `Image downloaded: ${urlWithoutBlob}`, | 654 | () => `Image downloaded: ${urlWithoutBlob}`, |
452 | ); | 655 | ); |
@@ -464,7 +667,7 @@ export class ContextMenuBuilder { | |||
464 | */ | 667 | */ |
465 | addCut( | 668 | addCut( |
466 | menu: Electron.CrossProcessExports.Menu, | 669 | menu: Electron.CrossProcessExports.Menu, |
467 | menuInfo: Electron.ContextMenuParams, | 670 | menuInfo: IContextMenuParams, |
468 | ) { | 671 | ) { |
469 | const webContents = this.getWebContents(); | 672 | const webContents = this.getWebContents(); |
470 | menu.append( | 673 | menu.append( |
@@ -484,7 +687,7 @@ export class ContextMenuBuilder { | |||
484 | */ | 687 | */ |
485 | addCopy( | 688 | addCopy( |
486 | menu: Electron.CrossProcessExports.Menu, | 689 | menu: Electron.CrossProcessExports.Menu, |
487 | menuInfo: Electron.ContextMenuParams, | 690 | menuInfo: IContextMenuParams, |
488 | ) { | 691 | ) { |
489 | const webContents = this.getWebContents(); | 692 | const webContents = this.getWebContents(); |
490 | menu.append( | 693 | menu.append( |
@@ -504,7 +707,7 @@ export class ContextMenuBuilder { | |||
504 | */ | 707 | */ |
505 | addPaste( | 708 | addPaste( |
506 | menu: Electron.CrossProcessExports.Menu, | 709 | menu: Electron.CrossProcessExports.Menu, |
507 | menuInfo: Electron.ContextMenuParams, | 710 | menuInfo: IContextMenuParams, |
508 | ) { | 711 | ) { |
509 | const webContents = this.getWebContents(); | 712 | const webContents = this.getWebContents(); |
510 | menu.append( | 713 | menu.append( |
@@ -521,7 +724,7 @@ export class ContextMenuBuilder { | |||
521 | 724 | ||
522 | addPastePlain( | 725 | addPastePlain( |
523 | menu: Electron.CrossProcessExports.Menu, | 726 | menu: Electron.CrossProcessExports.Menu, |
524 | menuInfo: Electron.ContextMenuParams, | 727 | menuInfo: IContextMenuParams, |
525 | ) { | 728 | ) { |
526 | if ( | 729 | if ( |
527 | menuInfo.editFlags.canPaste && | 730 | menuInfo.editFlags.canPaste && |
@@ -552,7 +755,7 @@ export class ContextMenuBuilder { | |||
552 | */ | 755 | */ |
553 | addInspectElement( | 756 | addInspectElement( |
554 | menu: Electron.CrossProcessExports.Menu, | 757 | menu: Electron.CrossProcessExports.Menu, |
555 | menuInfo: Electron.ContextMenuParams, | 758 | menuInfo: IContextMenuParams, |
556 | needsSeparator = true, | 759 | needsSeparator = true, |
557 | ) { | 760 | ) { |
558 | const webContents = this.getWebContents(); | 761 | const webContents = this.getWebContents(); |
@@ -609,6 +812,7 @@ export class ContextMenuBuilder { | |||
609 | */ | 812 | */ |
610 | goBack(menu: Electron.CrossProcessExports.Menu) { | 813 | goBack(menu: Electron.CrossProcessExports.Menu) { |
611 | const webContents = this.getWebContents(); | 814 | const webContents = this.getWebContents(); |
815 | |||
612 | menu.append( | 816 | menu.append( |
613 | new MenuItem({ | 817 | new MenuItem({ |
614 | label: this.stringTable.goBack(), | 818 | label: this.stringTable.goBack(), |
@@ -643,7 +847,7 @@ export class ContextMenuBuilder { | |||
643 | */ | 847 | */ |
644 | copyPageUrl( | 848 | copyPageUrl( |
645 | menu: Electron.CrossProcessExports.Menu, | 849 | menu: Electron.CrossProcessExports.Menu, |
646 | menuInfo: Electron.ContextMenuParams, | 850 | menuInfo: IContextMenuParams, |
647 | ) { | 851 | ) { |
648 | menu.append( | 852 | menu.append( |
649 | new MenuItem({ | 853 | new MenuItem({ |
@@ -652,7 +856,6 @@ export class ContextMenuBuilder { | |||
652 | click: () => { | 856 | click: () => { |
653 | clipboard.writeText(window.location.href); | 857 | clipboard.writeText(window.location.href); |
654 | this._sendNotificationOnClipboardEvent( | 858 | this._sendNotificationOnClipboardEvent( |
655 | // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. | ||
656 | menuInfo?.clipboardNotifications, | 859 | menuInfo?.clipboardNotifications, |
657 | () => `Page URL copied: ${window.location.href}`, | 860 | () => `Page URL copied: ${window.location.href}`, |
658 | ); | 861 | ); |
@@ -668,7 +871,7 @@ export class ContextMenuBuilder { | |||
668 | */ | 871 | */ |
669 | goToHomePage( | 872 | goToHomePage( |
670 | menu: Electron.CrossProcessExports.Menu, | 873 | menu: Electron.CrossProcessExports.Menu, |
671 | menuInfo: Electron.ContextMenuParams, | 874 | menuInfo: IContextMenuParams, |
672 | ) { | 875 | ) { |
673 | const baseURL = new window.URL(menuInfo.pageURL); | 876 | const baseURL = new window.URL(menuInfo.pageURL); |
674 | menu.append( | 877 | menu.append( |
@@ -691,7 +894,7 @@ export class ContextMenuBuilder { | |||
691 | */ | 894 | */ |
692 | openInBrowser( | 895 | openInBrowser( |
693 | menu: Electron.CrossProcessExports.Menu, | 896 | menu: Electron.CrossProcessExports.Menu, |
694 | menuInfo: Electron.ContextMenuParams, | 897 | menuInfo: IContextMenuParams, |
695 | ) { | 898 | ) { |
696 | menu.append( | 899 | menu.append( |
697 | new MenuItem({ | 900 | new MenuItem({ |
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 8c93da202..acf4f9f31 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -188,6 +188,9 @@ class RecipeController { | |||
188 | () => this.spellcheckerLanguage, | 188 | () => this.spellcheckerLanguage, |
189 | () => this.settings.app.searchEngine, | 189 | () => this.settings.app.searchEngine, |
190 | () => this.settings.app.clipboardNotifications, | 190 | () => this.settings.app.clipboardNotifications, |
191 | () => this.settings.app.enableTranslator, | ||
192 | () => this.settings.app.translatorEngine, | ||
193 | () => this.settings.app.translatorLanguage, | ||
191 | ); | 194 | ); |
192 | 195 | ||
193 | autorun(() => this.update()); | 196 | autorun(() => this.update()); |
@@ -293,6 +296,9 @@ class RecipeController { | |||
293 | ); | 296 | ); |
294 | debug('darkReaderSettigs', this.settings.service.darkReaderSettings); | 297 | debug('darkReaderSettigs', this.settings.service.darkReaderSettings); |
295 | debug('searchEngine', this.settings.app.searchEngine); | 298 | debug('searchEngine', this.settings.app.searchEngine); |
299 | debug('enableTranslator', this.settings.app.enableTranslator); | ||
300 | debug('translatorEngine', this.settings.app.translatorEngine); | ||
301 | debug('translatorLanguage', this.settings.app.translatorLanguage); | ||
296 | 302 | ||
297 | if (this.userscript && this.userscript.internal_setSettings) { | 303 | if (this.userscript && this.userscript.internal_setSettings) { |
298 | this.userscript.internal_setSettings(this.settings); | 304 | this.userscript.internal_setSettings(this.settings); |