From 53581f124a3480899458e8f560a15813c85b06cd Mon Sep 17 00:00:00 2001 From: viml kumar Date: Sun, 4 May 2025 14:56:50 +0530 Subject: [PATCH 1/3] feat: added hscii transliteration for indik langs using hsciistr npmjs lib --- manifests/manifest.json | 4 +- .../locales/utils/translateDescriptions.js | 13 +- site/supportedLanguages.ts | 16 ++- src/_locales/en/messages.json | 4 +- src/app/Background/TranslatorManager/index.ts | 28 +--- .../TranslatorsCacheStorage/index.ts | 40 +----- src/app/Background/index.ts | 17 +-- .../PageTranslator/PageTranslator.tsx | 12 +- .../SelectTranslator/SelectTranslator.tsx | 133 +++--------------- .../TextTranslator/TextTranslator.tsx | 39 ++--- src/app/ContentScript/index.ts | 12 +- src/app/index.ts | 34 +---- .../DictionaryButton/DictionaryButton.tsx | 1 - .../DictionaryButton/useDictionary.ts | 18 --- .../controls/LanguagePanel/LanguagePanel.tsx | 16 +-- .../layouts/LayoutFlow/LayoutFlow.tsx | 10 +- src/config.ts | 14 +- .../layout/OptionSection/OptionSection.tsx | 6 +- .../Editor/MonakoEditor/MonacoEditor.tsx | 34 +---- src/pages/popup/popup.tsx | 18 +-- .../backend/getUserLanguagePreferences.ts | 3 +- src/requests/backend/translate.ts | 1 - src/requests/backend/translators/data.ts | 38 +---- 23 files changed, 69 insertions(+), 442 deletions(-) diff --git a/manifests/manifest.json b/manifests/manifest.json index 1135203d..89e6b8df 100644 --- a/manifests/manifest.json +++ b/manifests/manifest.json @@ -2,9 +2,7 @@ "default_locale": "en", "name": "__MSG_ext_name__", "description": "__MSG_ext_description__", - "icons": { - "512": "static/logo/logo-icon.png" - }, + "icons": { "512": "static/logo/logo-icon.png" }, "permissions": ["storage", "tabs", "contextMenus", "scripting"], "content_scripts": [ { diff --git a/scripts/locales/utils/translateDescriptions.js b/scripts/locales/utils/translateDescriptions.js index ceeb3f03..e9862e49 100644 --- a/scripts/locales/utils/translateDescriptions.js +++ b/scripts/locales/utils/translateDescriptions.js @@ -1,6 +1,5 @@ const { writeFileSync, existsSync, readFileSync } = require('fs'); const path = require('path'); - const { getLocaleFilenames, getLocaleObject } = require('.'); let gptTranslator = null; @@ -8,11 +7,7 @@ const getGPTTranslator = async () => { if (gptTranslator === null) { gptTranslator = new Promise(async (res) => { const { ChatGPTUtils } = await import('../../ChatGPT.mjs'); - - const languageNames = new Intl.DisplayNames(['en'], { - type: 'language', - }); - + const languageNames = new Intl.DisplayNames(['en'], { type: 'language' }); const translator = (text, { from, to }) => `Translate this text below from ${languageNames.of( from, @@ -22,15 +17,12 @@ const getGPTTranslator = async () => { res(new ChatGPTUtils(translator)); }); } - return gptTranslator; }; - const descriptionsDir = path.resolve( path.join(__dirname, '../../../assets/stores/description'), ); const getDescriptionFilenameByCode = (code) => path.join(descriptionsDir, code + '.md'); - const translateDescription = async (descriptionSourceLanguage, descriptionLanguage) => { const writeUpdates = (lang, data) => { writeFileSync(getDescriptionFilenameByCode(lang), data); @@ -113,7 +105,6 @@ const translateDescriptions = async () => { console.log(`Skip "${lang}", because it is maintained language`); return false; } - if (process.env.STRATEGY === 'ADD_NEW') { const filePath = getDescriptionFilenameByCode(lang); const isFileExists = existsSync(filePath); @@ -121,10 +112,8 @@ const translateDescriptions = async () => { console.log(`Skip "${lang}", because file exists (strategy ADD_NEW)`); return false; } - return true; } - return true; }); diff --git a/site/supportedLanguages.ts b/site/supportedLanguages.ts index a8f30e68..3166ca3d 100644 --- a/site/supportedLanguages.ts +++ b/site/supportedLanguages.ts @@ -6,19 +6,23 @@ export const languages = [ 'de', 'ru', 'hi', - 'ar', 'bn', + 'te', + 'kn', + 'ml', + 'ta', + 'si', + 'or', + 'pa', + 'gu', + 'ar', 'pt', 'vi', 'tr', 'mr', - 'te', - 'pa', 'ko', - 'ta', 'ur', 'jv', - 'gu', 'zh', 'ja', -]; +]; // hscii // https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 235949c6..e5296a0f 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -26,9 +26,7 @@ "common_retry": { "message": "Retry" }, - "common_translate": { - "message": "Translate" - }, + "common_translate": { "message": "Translate" }, "component_hotkey_placeholder": { "message": "Click to record hotkeys" }, diff --git a/src/app/Background/TranslatorManager/index.ts b/src/app/Background/TranslatorManager/index.ts index 440d6df3..947e49ce 100644 --- a/src/app/Background/TranslatorManager/index.ts +++ b/src/app/Background/TranslatorManager/index.ts @@ -13,9 +13,7 @@ import { TranslatorsMap } from '..'; export type Config = Pick; -/** - * Build and manage a translation scheduler - */ +/** * Build and manage a translation scheduler */ export class TranslatorManager { private config: Config; private translators: Translators; @@ -23,17 +21,14 @@ export class TranslatorManager> { return this.getTranslatorInstance(false); } - - /** - * Return configured translation scheduler - */ + /** * Return configured translation scheduler */ public getScheduler() { return this.getTranslationSchedulerInstance(); } - private schedulerInstance: IScheduler | null = null; private getTranslationSchedulerInstance(forceCreate = false) { if (this.schedulerInstance === null || forceCreate) { const translator = this.getTranslatorInstance(true); - const { useCache, ...schedulerConfig } = this.config.scheduler; - const scheduler = new Scheduler(translator, schedulerConfig); - let schedulerInstance: IScheduler = scheduler; if (useCache) { // Wrap scheduler by cache @@ -87,29 +71,23 @@ export class TranslatorManager> | null = null; private getTranslatorInstance(forceCreate: boolean) { if (!forceCreate && this.translator !== null) return this.translator; - const translatorClass = this.getTranslatorClass(); this.translator = new translatorClass() as InstanceType< RecordValues >; - return this.translator; } - private getCacheInstance() { const { translatorModule, cache } = this.config; return new TranslatorsCacheStorage(translatorModule, cache); } - private getTranslatorClass(): RecordValues { const { translatorModule } = this.config; - const translators = this.getTranslators(); const translatorClass = translators[translatorModule]; if (translatorClass === undefined) { throw new Error(`Not found translator "${translatorModule}"`); } - return translatorClass as RecordValues; } } diff --git a/src/app/Background/TranslatorsCacheStorage/index.ts b/src/app/Background/TranslatorsCacheStorage/index.ts index f9d34160..ed2b28c7 100644 --- a/src/app/Background/TranslatorsCacheStorage/index.ts +++ b/src/app/Background/TranslatorsCacheStorage/index.ts @@ -3,74 +3,48 @@ import * as IDB from 'idb/with-async-ittr'; import { ITranslation } from '../../../types/translation/Translation'; export interface TranslatorDBSchema extends IDB.DBSchema { - tableName: { - key: string; - value: ITranslation; - indexes: { - originalText: string; - }; - }; + tableName: { key: string; value: ITranslation; indexes: { originalText: string } }; } - interface Options { ignoreCase: boolean; } - -/** - * Helper type to cast table name string - */ +/** * Helper type to cast table name string */ type TableName = 'tableName'; - -/** - * Manage cache by translator id - */ +/** * Manage cache by translator id */ export class TranslatorsCacheStorage { private dbPromise: Promise> | null = null; - private readonly tableName: string; private readonly options: Options; - constructor(id: string, options: Partial = {}) { this.tableName = id; - this.options = { - ignoreCase: true, - ...options, - }; + this.options = { ignoreCase: true, ...options }; } - private getDB() { if (this.dbPromise === null) { const unsetPromise = () => { this.dbPromise = null; }; - const DBName = 'translatorsCache'; const id = this.tableName; - const dbPromise = IDB.openDB(DBName).then((db) => { // Prevent versionchange blocking db.addEventListener('versionchange', () => { db.close(); unsetPromise(); }); - // Return DB if storage exist if (db.objectStoreNames.contains(id as TableName)) return db; - // Otherwise create storage const nextVersion = db.version + 1; - return IDB.openDB(DBName, nextVersion, { upgrade(db) { const store = db.createObjectStore(id as TableName, { keyPath: 'id', autoIncrement: true, }); - store.createIndex('originalText', 'originalText', { unique: true, }); - // Prevent versionchange blocking db.addEventListener('versionchange', () => { db.close(); @@ -86,11 +60,9 @@ export class TranslatorsCacheStorage { if (this.dbPromise === dbPromise) { this.dbPromise = null; } - throw reason; }); }); - this.dbPromise = dbPromise; } @@ -101,7 +73,6 @@ export class TranslatorsCacheStorage { return this.getDB().then(async (db) => { const tx = db.transaction(this.tableName as TableName, 'readonly'); const index = tx.store.index('originalText'); - let result: string | null = null; const sourceText = this.options.ignoreCase ? text.toLowerCase() : text; for await (const cursor of index.iterate(sourceText)) { @@ -110,7 +81,6 @@ export class TranslatorsCacheStorage { break; } } - await tx.done; return result; }); @@ -119,7 +89,6 @@ export class TranslatorsCacheStorage { public set(originalText: string, translatedText: string, from: string, to: string) { return this.getDB().then(async (db) => { const tx = db.transaction(this.tableName as TableName, 'readwrite'); - const transformedOriginalText = this.options.ignoreCase ? originalText.toLowerCase() : originalText; @@ -132,7 +101,6 @@ export class TranslatorsCacheStorage { await tx.done; }); } - public clear() { // TODO: remove DB instead of clear return this.getDB().then((db) => db.clear(this.tableName as TableName)); diff --git a/src/app/Background/index.ts b/src/app/Background/index.ts index 2670c486..f0544baa 100644 --- a/src/app/Background/index.ts +++ b/src/app/Background/index.ts @@ -48,10 +48,8 @@ export class Background { if (this.translateManagerPromise === null) { const promiseWithControls = createPromiseWithControls(); - // Set promise this.translateManagerPromise = promiseWithControls; - // Clear promise promiseWithControls.promise.finally(() => { if (promiseWithControls === this.translateManagerPromise) { @@ -59,17 +57,13 @@ export class Background { } }); } - return this.translateManagerPromise.promise; } - return this.translateManager; } - public getTTSManager() { return this.ttsManager; } - private ttsController: TTSController | null = null; public async getTTSController() { if (this.ttsController === null) { @@ -77,10 +71,8 @@ export class Background { const config = $config.getState(); this.ttsController = new TTSController(this.ttsManager, config.ttsModule); } - return this.ttsController; } - public async start() { const $config = await this.config.getObservableStore(); const $translateManagerConfig = createSelector( @@ -90,29 +82,22 @@ export class Background { translatorModule, cache, }), - { - updateFilter: (update, state) => !isEqual(update, state), - }, + { updateFilter: (update, state) => !isEqual(update, state) }, ); - // Build translators list const translators: TranslatorsMap = await getTranslatorsClasses(); - // Update config of translate manager $translateManagerConfig.watch((config) => { if (this.translateManager === null) { this.translateManager = new TranslatorManager(config, translators); - // Return a scheduler instance for awaiters if (this.translateManagerPromise !== null) { this.translateManagerPromise.resolve(this.translateManager); } return; } - this.translateManager.setConfig(config); }); - // Update TTS module $config .map(({ ttsModule }) => ttsModule) diff --git a/src/app/ContentScript/PageTranslator/PageTranslator.tsx b/src/app/ContentScript/PageTranslator/PageTranslator.tsx index 609241c1..f3ec8eae 100644 --- a/src/app/ContentScript/PageTranslator/PageTranslator.tsx +++ b/src/app/ContentScript/PageTranslator/PageTranslator.tsx @@ -7,11 +7,7 @@ import { translate } from '../../../requests/backend/translate'; import { OriginalTextPopup } from './components/OriginalTextPopup/OriginalTextPopup'; import { pageTranslatorStatsUpdated } from './requests/pageTranslatorStatsUpdated'; -export type PageTranslatorStats = { - resolved: number; - rejected: number; - pending: number; -}; +export type PageTranslatorStats = { resolved: number; rejected: number; pending: number }; function isBlockElement(element: Element) { const blockTypes = ['block', 'flex', 'grid', 'table', 'table-row', 'list-item']; @@ -32,7 +28,6 @@ export class PageTranslator { rejected: 0, pending: 0, }; - private config: PageTranslatorConfig = {}; private nodesTranslatorConfig: NodesTranslatorConfig = {}; constructor(config: NodesTranslatorConfig & PageTranslatorConfig) { @@ -72,23 +67,20 @@ export class PageTranslator { if (localContext !== this.translateContext) { throw new Error('Outdated context'); } - localTranslateState.pending++; this.translateStateUpdate(); - + //hscii return translate(text, from, to, { priority }) .then((translatedText) => { if (localContext === this.translateContext) { localTranslateState.resolved++; } - return translatedText; }) .catch((reason) => { if (localContext === this.translateContext) { localTranslateState.rejected++; } - throw reason; }) .finally(() => { diff --git a/src/app/ContentScript/SelectTranslator/SelectTranslator.tsx b/src/app/ContentScript/SelectTranslator/SelectTranslator.tsx index 4366ebe8..9cf08ad3 100644 --- a/src/app/ContentScript/SelectTranslator/SelectTranslator.tsx +++ b/src/app/ContentScript/SelectTranslator/SelectTranslator.tsx @@ -6,79 +6,38 @@ import { translate } from '../../../requests/backend/translate'; import { TextTranslatorPopup } from './components/TextTranslatorPopup/TextTranslatorPopup'; export interface Options { - /** - * Key modifiers to activate translate of selected text - */ + /** * Key modifiers to activate translate of selected text */ modifiers: Array<'ctrlKey' | 'altKey' | 'shiftKey' | 'metaKey'>; - - /** - * Skip when pointerdown not on the selected text - */ + /** * Skip when pointerdown not on the selected text */ strictSelection: boolean; - - /** - * Don't show translate button and translate at once - */ + /** * Don't show translate button and translate at once */ quickTranslate: boolean; - - /** - * Page language for translate direction - */ + /** * Page language for translate direction */ pageLanguage?: string; - - /** - * Detected language is firstly than page language - */ + /** * Detected language is firstly than page language */ detectedLangFirst: boolean; - - /** - * Use auto detection for `from` direction - */ + /** * Use auto detection for `from` direction */ isUseAutoForDetectLang: boolean; - - /** - * Remember translate direction - */ + /** * Remember translate direction */ rememberDirection: boolean; - - /** - * CSS property for popup - */ + /** * CSS property for popup */ zIndex?: number; - - /** - * Hide translate button after delay when specified positive number - */ + /** * Hide translate button after delay when specified positive number */ timeoutForHideButton?: number; - - /** - * Useful for keyboard navigation - */ + /** * Useful for keyboard navigation */ focusOnTranslateButton?: boolean; - - /** - * Show translate block once for each text selection - */ + /** * Show translate block once for each text selection */ showOnceForSelection?: boolean; - - /** - * Show block with original text - */ + /** * Show block with original text */ showOriginalText: boolean; - enableTranslateFromContextMenu?: boolean; } - export const getSelectedTextOfInput = (elm: HTMLInputElement | HTMLTextAreaElement) => { const { selectionStart, selectionEnd } = elm; - if (selectionStart === null || selectionEnd === null) return ''; return elm.value.slice(selectionStart, selectionEnd); }; - -/** - * This wrapper on component need to allow convenient manage state - */ +/** * This wrapper on component need to allow convenient manage state */ export class SelectTranslator { private options: Options = { modifiers: ['ctrlKey'], @@ -91,7 +50,6 @@ export class SelectTranslator { isUseAutoForDetectLang: true, enableTranslateFromContextMenu: false, }; - constructor(options?: Partial) { if (options !== undefined) { for (const key in options) { @@ -99,7 +57,6 @@ export class SelectTranslator { } } } - // Flag which set while every selection event and reset while button shown private unhandledSelection = false; private selectionTarget: HTMLElement | null = null; @@ -107,68 +64,51 @@ export class SelectTranslator { this.unhandledSelection = true; this.selectionTarget = evt.target instanceof HTMLElement ? evt.target : null; }; - private readonly shadowRoot = new ShadowDOMContainerManager({ styles: ['contentscript.css'], }); - public start() { if (this.shadowRoot.getRootNode() !== null) { throw new Error('Already started'); } - this.shadowRoot.createRootNode(); const root = this.shadowRoot.getRootNode() as HTMLElement; - // Add event listeners root.addEventListener('keydown', this.keyDown); document.addEventListener('selectionchange', this.selectionFlagUpdater); - document.addEventListener('pointerdown', this.pointerDown); document.addEventListener('pointerup', this.pointerUp); - document.addEventListener('touchstart', this.pointerDown); document.addEventListener('touchend', this.pointerUp); - this.mountEmptyComponent(); } - public stop() { const root = this.shadowRoot.getRootNode(); if (root === null) { throw new Error('Not started'); } - // Remove event listeners root.removeEventListener('keydown', this.keyDown); document.removeEventListener('selectionchange', this.selectionFlagUpdater); - document.removeEventListener('pointerdown', this.pointerDown); document.removeEventListener('pointerup', this.pointerUp); - document.removeEventListener('touchstart', this.pointerDown); document.removeEventListener('touchend', this.pointerUp); - // Unmount component and remove root node this.shadowRoot.unmountComponent(); this.shadowRoot.removeRootNode(); } - public isRun() { return this.shadowRoot.getRootNode() !== null; } - public translateSelectedText = () => { this.hidePopup(); - const { x, y } = this.lastPointerPosition || { x: window.scrollX, y: window.scrollY, }; - this.getSelectedText().then((selection) => { let text: string | null = null; - // TODO: #refactor move this logic to one method `getSelectedText(target?: Node)` if (selection !== null) { text = selection.text; @@ -179,20 +119,16 @@ export class SelectTranslator { ) { text = getSelectedTextOfInput(this.selectionTarget); } - if (text !== null) { this.showPopup(text, x, y); } }); }; - private getSelectedText = () => new Promise<{ selection: Selection; text: string } | null>((res) => { const root = this.shadowRoot.getRootNode(); - this.context = Symbol('context'); const context = this.context; - // Get selected text in next frame requestAnimationFrame(() => { if (context !== this.context) { @@ -200,15 +136,12 @@ export class SelectTranslator { return; } this.context = Symbol('context'); - const selection = window.getSelection(); - // Skip empty selection if (selection === null) { res(null); return; } - // Skip if selected a text inside root node if ( root === null || @@ -216,7 +149,6 @@ export class SelectTranslator { root.contains(selection.focusNode) ) return; - const selectedText = selection.toString(); res(selectedText.length > 0 ? { selection, text: selectedText } : null); }); @@ -226,46 +158,29 @@ export class SelectTranslator { private keyDown = (evt: KeyboardEvent) => { evt.stopImmediatePropagation(); }; - // NOTE: maybe it should be removed after start use popup - /** - * Close popup by click outside the root - */ + /** * Close popup by click outside the root */ private pointerDown = (evt: PointerEvent | TouchEvent) => { const root = this.shadowRoot.getRootNode(); if (root !== null && evt.target instanceof Node && root.contains(evt.target)) return; - this.hidePopup(); }; - private context = Symbol('context'); - private lastPointerPosition: { x: number; y: number } | null = null; - - /** - * Open popup by text selection on the page - */ + /** * Open popup by text selection on the page */ private pointerUp = async (evt: PointerEvent | TouchEvent) => { await new Promise((res) => setTimeout(res, 10)); - const getIsTouchEvt = (evt: Event): evt is TouchEvent => evt.type === 'touchstart' || evt.type === 'touchend'; const isTouchEvt = getIsTouchEvt(evt); - // Reject if press not left button or not just touch // Codes list: https://www.w3.org/TR/pointerevents1/#h5_chorded-button-interactions if (!isTouchEvt && evt.button !== 0) return; - const { pageX, pageY } = isTouchEvt ? evt.changedTouches[0] : evt; - this.lastPointerPosition = { - x: pageX, - y: pageY, - }; - + this.lastPointerPosition = { x: pageX, y: pageY }; // Skip when enabled translation with context menu if (this.options.enableTranslateFromContextMenu) return; - // Check modifier keys const requiredModifierKeys = this.options.modifiers; if ( @@ -273,28 +188,21 @@ export class SelectTranslator { !requiredModifierKeys.every((value) => evt[value]) ) return; - const target = evt.target; const root = this.shadowRoot.getRootNode(); - // Skip events inside root node if (root === null || (target instanceof Node && root.contains(target))) return; - this.getSelectedText().then((selectedTextObj) => { let text: string | null = null; - if (selectedTextObj !== null) { // Use selected text on page text = selectedTextObj.text; - const { selection } = selectedTextObj; - // Skip when pointerdown not on the selected text if (this.options.strictSelection && selection.focusNode instanceof Text) { const parent = selection.focusNode.parentElement; if (parent !== null && parent !== target) return; } - // Skip if it shown not first time if (this.options.showOnceForSelection && !this.unhandledSelection) return; } else if ( @@ -305,21 +213,16 @@ export class SelectTranslator { // Use selected text in input text = getSelectedTextOfInput(this.selectionTarget); } - if (text !== null) { this.showPopup(text, pageX, pageY); } }); }; - private showPopup = (text: string, x: number, y: number) => { const trimmedText = text.trim(); - if (trimmedText.length === 0) return; - // Update selection value this.unhandledSelection = false; - const { pageLanguage, quickTranslate, @@ -332,9 +235,7 @@ export class SelectTranslator { showOriginalText, enableTranslateFromContextMenu, } = this.options; - const isForceQuickTranslate = enableTranslateFromContextMenu; - this.shadowRoot.mountComponent( , ); }; - private hidePopup = () => { this.mountEmptyComponent(); }; - private mountEmptyComponent = () => { this.shadowRoot.mountComponent(); }; diff --git a/src/app/ContentScript/SelectTranslator/components/TextTranslatorPopup/TextTranslator/TextTranslator.tsx b/src/app/ContentScript/SelectTranslator/components/TextTranslatorPopup/TextTranslator/TextTranslator.tsx index 181ccf4c..177f5976 100644 --- a/src/app/ContentScript/SelectTranslator/components/TextTranslatorPopup/TextTranslator/TextTranslator.tsx +++ b/src/app/ContentScript/SelectTranslator/components/TextTranslatorPopup/TextTranslator/TextTranslator.tsx @@ -54,58 +54,43 @@ export const TextTranslator: FC = ({ const [from, setFrom] = useState(); const [to, setTo] = useState(); const [translatorFeatures, setTranslatorFeatures] = useState(); - const [originalText, setOriginalText] = useState(text); const [translatedText, setTranslatedText] = useState(null); const [error, setError] = useState(null); - const translateContext = useRef(Symbol('TranslateContext')); const translateText = useCallback(() => { // NOTE: maybe worth handle this error if (from === undefined || to === undefined) { throw Error(`Call to translate method with invalid direction: ${from}-${to}`); } - translateContext.current = Symbol('TranslateContext'); const context = translateContext.current; - setTranslatedText(null); setError(null); - translate(originalText, from, to) .then((translatedText) => { if (context !== translateContext.current) return; - setTranslatedText(translatedText); setError(null); - addTranslationHistoryEntry({ origin: TRANSLATION_ORIGIN.USER_INPUT, - translation: { - from, - to, - originalText, - translatedText, - }, + translation: { from, to, originalText, translatedText }, }); }) .catch((reason) => { if (context !== translateContext.current) return; - let error = 'Unknown error'; if (typeof reason === 'string') { error = reason; } else if (reason instanceof Error) { error = reason.message; } - setTranslatedText(null); setError(error); console.error(error); }) .finally(() => { if (context !== translateContext.current) return; - translateContext.current = Symbol('TranslateContext'); }); }, [from, originalText, to, translate]); @@ -113,33 +98,25 @@ export const TextTranslator: FC = ({ const swapHandler = useCallback( ({ from, to }: { from: string; to: string }) => { if (translatedText === null) return; - setFrom(from); setTo(to); setOriginalText(translatedText); setTranslatedText(null); }, [translatedText], - ); + ); // hscii const dictionaryData: ITranslation | null = useMemo(() => { if (translatedText === null || from === undefined || to === undefined) return null; - - return { - from, - to, - originalText, - translatedText, - }; + return { from, to, originalText, translatedText }; }, [from, originalText, to, translatedText]); - // Init const isUnmount = useRef(false); useEffect(() => { getTranslatorFeatures().then( async ({ supportedLanguages, isSupportAutodetect }) => { - const userLanguage = await getUserLanguagePreferences(); + const userLanguage = await getUserLanguagePreferences(); // hscii let from: string | undefined; @@ -251,7 +228,7 @@ export const TextTranslator: FC = ({ isSupportAutodetect, }); setFrom(from); - setTo(userLanguage); + setTo(userLanguage); // hscii } }, ); @@ -418,12 +395,14 @@ export const TextTranslator: FC = ({ className={cnTextTranslator('Details')} > + {' '} {getMessage( 'inlineTranslator_showOriginalText', - )} + )}{' '}

- {originalText} + {' '} + {originalText}{' '}

diff --git a/src/app/ContentScript/index.ts b/src/app/ContentScript/index.ts index 34bde8d9..851ad5c5 100644 --- a/src/app/ContentScript/index.ts +++ b/src/app/ContentScript/index.ts @@ -6,32 +6,22 @@ export class ContentScript { public static async main() { const config = new ClientConfig(); const contentscript = new ContentScript(config); - await contentscript.start(); } - private config: ClientConfig; constructor(config: ClientConfig) { this.config = config; } - private pageTranslationContext: PageTranslationContext | null = null; public async start() { // Skip for already started instances if (this.pageTranslationContext !== null) return; - const $config = await this.config.getStore(); - const pageTranslationContext = new PageTranslationContext($config); this.pageTranslationContext = pageTranslationContext; - await pageTranslationContext.start(); - requestHandlers.forEach((factory) => { - factory({ - $config, - pageTranslationContext, - }); + factory({ $config, pageTranslationContext }); }); } } diff --git a/src/app/index.ts b/src/app/index.ts index f3a9e279..769a7da7 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -29,25 +29,16 @@ export class App { public static async main() { const onInstalled = createEvent(); browser.runtime.onInstalled.addListener(onInstalled); - const $onInstalledData = createStore(null); $onInstalledData.on(onInstalled, (_, onInstalledData) => onInstalledData); - // Migrate data await migrateAll(); - const config = new ConfigStorage(defaultConfig); const observableConfig = new ObservableAsyncStorage(config); const background = new Background(observableConfig); - - const app = new App({ - config: observableConfig, - background, - $onInstalledData, - }); + const app = new App({ config: observableConfig, background, $onInstalledData }); await app.start(); } - private readonly config: ObservableAsyncStorage; private readonly background: Background; private readonly $onInstalledData: Store; @@ -64,30 +55,23 @@ export class App { this.background = background; this.$onInstalledData = $onInstalledData; } - private isStarted = false; public async start() { if (this.isStarted) { throw new Error('Application already started'); } - this.isStarted = true; - await this.setupOffscreenDocuments(); await this.background.start(); - await this.setupRequestHandlers(); await this.handleConfigUpdates(); - this.$onInstalledData.watch(this.onInstalled); } - private async setupOffscreenDocuments() { // Setup sandboxed iframes if (isChromium()) { // Currently `offscreen` API is non standard, so we cast type const offscreen = (globalThis as any).chrome.offscreen; - // We may have only one offscreen document, but we need more, // so we create only one "main" document, that creates embedded iframes try { @@ -108,28 +92,21 @@ export class App { customTranslatorsFactory(); } } - private async setupRequestHandlers() { // TODO: debug this condition and remove or move on top // Prevent run it again on other pages, such as options page if (!isFirefox() || isBackgroundContext()) { requestHandlers.forEach((factory) => { - factory({ - config: this.config, - backgroundContext: this.background, - }); + factory({ config: this.config, backgroundContext: this.background }); }); } } - private async handleConfigUpdates() { const $appConfig = await this.config.getObservableStore(); - // Send update event $appConfig.watch((config) => { sendAppConfigUpdateEvent(config); }); - // Update icon const appThemeControl = new AppThemeControl(); $appConfig @@ -137,7 +114,6 @@ export class App { .watch((appIcon) => { appThemeControl.setAppIconPreferences(appIcon); }); - // Clear cache while disable $appConfig .map((config) => config.scheduler.useCache) @@ -146,7 +122,6 @@ export class App { clearCache(); } }); - // Clear TextTranslator state const textTranslatorStorage = new TextTranslatorStorage(); $appConfig @@ -156,7 +131,6 @@ export class App { textTranslatorStorage.forgetText(); } }); - // Configure context menu const translateSelectionContextMenu = new TranslateSelectionContextMenu(); $appConfig @@ -184,16 +158,13 @@ export class App { } }); } - private onInstalled = async (details: OnInstalledData) => { if (details === null) return; - // Inject content scripts for chrome, to make page translation available just after install if (isChromium()) { const tabs = await getAllTabs(); tabs.forEach((tab) => { if (tab.status === 'unloaded') return; - // Ignore special URLs if ( !tab.url || @@ -201,7 +172,6 @@ export class App { tab.url.startsWith('https://chrome.google.com') ) return; - browser.scripting.executeScript({ target: { tabId: tab.id }, files: ['contentscript.js'], diff --git a/src/components/controls/DictionaryButton/DictionaryButton.tsx b/src/components/controls/DictionaryButton/DictionaryButton.tsx index deb4a3f8..a4e34384 100644 --- a/src/components/controls/DictionaryButton/DictionaryButton.tsx +++ b/src/components/controls/DictionaryButton/DictionaryButton.tsx @@ -11,7 +11,6 @@ export const DictionaryButton: FC<{ translation: ITranslation | null }> = ({ translation, }) => { const dictionary = useDictionary(translation); - return (
@@ -202,7 +189,8 @@ export const PageTranslator: FC = ({
- {getMessage('pageTranslator_option_autoTranslate')} + {' '} + {getMessage('pageTranslator_option_autoTranslate')}{' '}