diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 8fe408ee9a..a7b9a3324b 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1463,7 +1463,7 @@ export class Backend { /** @type {import('translator').FindTermsMode} */ const mode = 'simple'; const options = this._getProfileOptions(optionsContext, false); - const details = {matchType: /** @type {import('translation').FindTermsMatchType} */ ('exact'), deinflect: true}; + const details = {matchType: /** @type {import('translation').FindTermsMatchType} */ ('exact'), deinflect: true, reading: null}; const findTermsOptions = this._getTranslatorFindTermsOptions(mode, details, options); /** @type {import('api').ParseTextLine[]} */ const results = []; @@ -2455,7 +2455,7 @@ export class Backend { * @returns {import('translation').FindTermsOptions} An options object. */ _getTranslatorFindTermsOptions(mode, details, options) { - let {matchType, deinflect} = details; + let {matchType, deinflect, reading} = details; if (typeof matchType !== 'string') { matchType = /** @type {import('translation').FindTermsMatchType} */ ('exact'); } if (typeof deinflect !== 'boolean') { deinflect = true; } const enabledDictionaryMap = this._getTranslatorEnabledDictionaryMap(options); @@ -2484,6 +2484,7 @@ export class Backend { return { matchType, deinflect, + reading, mainDictionary, sortFrequencyDictionary, sortFrequencyDictionaryOrder, diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 3f6d893a8d..1c31c97805 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1265,14 +1265,15 @@ export class Display extends EventDispatcher { /** * @param {boolean} isKanji * @param {string} source + * @param {string | null} reading * @param {boolean} wildcardsEnabled * @param {import('settings').OptionsContext} optionsContext * @returns {Promise} */ - async _findDictionaryEntries(isKanji, source, wildcardsEnabled, optionsContext) { + async _findDictionaryEntries(isKanji, source, reading, wildcardsEnabled, optionsContext) { /** @type {import('dictionary').DictionaryEntry[]} */ let dictionaryEntries = []; - const {findDetails, source: source2} = this._getFindDetails(source, wildcardsEnabled); + const {findDetails, source: source2} = this._getFindDetails(source, reading, wildcardsEnabled); if (isKanji) { dictionaryEntries = await this._application.api.kanjiFind(source, optionsContext); if (dictionaryEntries.length > 0) { return dictionaryEntries; } @@ -1289,12 +1290,13 @@ export class Display extends EventDispatcher { /** * @param {string} source + * @param {string | null} reading * @param {boolean} wildcardsEnabled * @returns {{findDetails: import('api').FindTermsDetails, source: string}} */ - _getFindDetails(source, wildcardsEnabled) { + _getFindDetails(source, reading, wildcardsEnabled) { /** @type {import('api').FindTermsDetails} */ - const findDetails = {}; + const findDetails = {reading}; if (wildcardsEnabled) { const match = /^([*\uff0a]*)([\w\W]*?)([*\uff0a]*)$/.exec(source); if (match !== null) { @@ -1319,6 +1321,7 @@ export class Display extends EventDispatcher { async _setContentTermsOrKanji(type, urlSearchParams, token) { const lookup = (urlSearchParams.get('lookup') !== 'false'); const wildcardsEnabled = (urlSearchParams.get('wildcards') !== 'off'); + const reading = urlSearchParams.get('reading'); const hasEnabledDictionaries = this._options ? this._options.dictionaries.some(({enabled}) => enabled) : false; // Set query @@ -1358,7 +1361,7 @@ export class Display extends EventDispatcher { let {dictionaryEntries} = content; if (!Array.isArray(dictionaryEntries)) { - dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, wildcardsEnabled, optionsContext) : []; + dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, reading, wildcardsEnabled, optionsContext) : []; if (this._setContentToken !== token) { return; } content.dictionaryEntries = dictionaryEntries; changeHistory = true; diff --git a/ext/js/display/structured-content-generator.js b/ext/js/display/structured-content-generator.js index 7074243a17..b64aa1d3ae 100644 --- a/ext/js/display/structured-content-generator.js +++ b/ext/js/display/structured-content-generator.js @@ -436,10 +436,6 @@ export class StructuredContentGenerator { _createLinkElement(content, dictionary, language) { let {href} = content; const internal = href.startsWith('?'); - if (internal) { - href = `${location.protocol}//${location.host}/search.html${href.length > 1 ? href : ''}`; - } - const node = /** @type {HTMLAnchorElement} */ (this._createElement('a', 'gloss-link')); node.dataset.external = `${!internal}`; @@ -460,6 +456,31 @@ export class StructuredContentGenerator { node.appendChild(icon); } + if (internal) { + let hasFurigana = false; + let reading = ''; + for (const childNode of text.childNodes) { + if (childNode instanceof HTMLElement) { + const nodeReading = childNode.querySelector('.gloss-sc-rt')?.textContent; + if (nodeReading && nodeReading.length > 0) { + reading += nodeReading; + hasFurigana = true; + } + } else { + reading += childNode.textContent ?? ''; + } + } + + let query = ''; + if (href.length > 1) { + query = href; + if (reading.length > 0 && hasFurigana) { + query += `&reading=${reading}`; + } + } + href = `${location.protocol}//${location.host}/search.html${query}`; + } + this._contentManager.prepareLink(node, href, internal); return node; } diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 21719da3d1..af1e1af013 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -76,7 +76,7 @@ export class Translator { * @returns {Promise<{dictionaryEntries: import('dictionary').TermDictionaryEntry[], originalTextLength: number}>} An object containing dictionary entries and the length of the original source text. */ async findTerms(mode, text, options) { - const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language} = options; + const {reading, enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language} = options; const tagAggregator = new TranslatorTagAggregator(); let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, options, tagAggregator); @@ -220,7 +220,7 @@ export class Translator { * @returns {Promise<{dictionaryEntries: import('translation-internal').TermDictionaryEntry[], originalTextLength: number}>} */ async _findTermsInternal(text, options, tagAggregator) { - const {removeNonJapaneseCharacters, enabledDictionaryMap} = options; + const {reading, removeNonJapaneseCharacters, enabledDictionaryMap} = options; if (removeNonJapaneseCharacters && (['ja', 'zh', 'yue'].includes(options.language))) { text = this._getJapaneseChineseOnlyText(text); } @@ -230,16 +230,17 @@ export class Translator { const deinflections = await this._getDeinflections(text, options); - return this._getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator); + return this._getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator, reading); } /** * @param {import('translation-internal').DatabaseDeinflection[]} deinflections * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string | null} reading * @returns {{dictionaryEntries: import('translation-internal').TermDictionaryEntry[], originalTextLength: number}} */ - _getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator) { + _getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator, reading) { let originalTextLength = 0; /** @type {import('translation-internal').TermDictionaryEntry[]} */ const dictionaryEntries = []; @@ -268,6 +269,12 @@ export class Translator { } } else { const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator); + if (reading) { + const headwordReadings = new Set(dictionaryEntry.headwords.map((headword) => headword.reading)); + if (!headwordReadings.has(reading)) { + continue; + } + } dictionaryEntries.push(dictionaryEntry); ids.add(id); } diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 46be7938eb..0b520d5e65 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -48,6 +48,7 @@ import type { export type FindTermsDetails = { matchType?: Translation.FindTermsMatchType; deinflect?: boolean; + reading: string | null; }; export type ParseTextResultItem = { diff --git a/types/ext/translation.d.ts b/types/ext/translation.d.ts index 4d60507ce3..6c9ce394b9 100644 --- a/types/ext/translation.d.ts +++ b/types/ext/translation.d.ts @@ -68,6 +68,10 @@ export type FindTermsOptions = { * Whether or not deinflection should be performed. */ deinflect: boolean; + /** + * Exact reading of the term. + */ + reading: string | null; /** * The name of the primary dictionary to search. */