Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
at lambda-fork/main 147 lines 6.1 kB view raw
1/* 2 * Copyright (C) 2023-2025 Yomitan Authors 3 * Copyright (C) 2020-2022 Yomichan Authors 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19// eslint-disable-next-line no-template-curly-in-string 20const placeholder = '${title}'; 21 22/** 23 * @template {import('test/translator').OptionsType} T 24 * @param {T} type 25 * @param {import('test/translator').OptionsPresetObject} optionsPresets 26 * @param {import('test/translator').OptionsList} optionsArray 27 * @returns {import('test/translator').OptionsPresetGeneric<T>} 28 * @throws {Error} 29 */ 30function getCompositePreset(type, optionsPresets, optionsArray) { 31 const preset = /** @type {import('test/translator').OptionsPresetGeneric<T>} */ ({type}); 32 if (!Array.isArray(optionsArray)) { optionsArray = [optionsArray]; } 33 for (const entry of optionsArray) { 34 switch (typeof entry) { 35 case 'string': 36 { 37 if (!Object.prototype.hasOwnProperty.call(optionsPresets, entry)) { 38 throw new Error('Options preset not found'); 39 } 40 const preset2 = optionsPresets[entry]; 41 if (preset2.type !== type) { 42 throw new Error('Invalid options preset type'); 43 } 44 Object.assign(preset, structuredClone(preset2)); 45 } 46 break; 47 case 'object': 48 if (entry.type !== type) { 49 throw new Error('Invalid options preset type'); 50 } 51 Object.assign(preset, structuredClone(entry)); 52 break; 53 default: 54 throw new Error('Invalid options type'); 55 } 56 } 57 return preset; 58} 59 60 61/** 62 * @param {string} dictionaryName 63 * @param {import('test/translator').OptionsPresetObject} optionsPresets 64 * @param {import('test/translator').OptionsList} optionsArray 65 * @returns {import('translation').FindKanjiOptions} 66 */ 67export function createFindKanjiOptions(dictionaryName, optionsPresets, optionsArray) { 68 const preset = getCompositePreset('kanji', optionsPresets, optionsArray); 69 70 /** @type {import('translation').KanjiEnabledDictionaryMap} */ 71 const enabledDictionaryMap = new Map(); 72 const presetEnabledDictionaryMap = preset.enabledDictionaryMap; 73 if (Array.isArray(presetEnabledDictionaryMap)) { 74 for (const [key, value] of presetEnabledDictionaryMap) { 75 enabledDictionaryMap.set(key === placeholder ? dictionaryName : key, value); 76 } 77 } 78 79 return { 80 enabledDictionaryMap, 81 removeNonJapaneseCharacters: !!preset.removeNonJapaneseCharacters, 82 }; 83} 84 85/** 86 * @param {string} dictionaryName 87 * @param {import('test/translator').OptionsPresetObject} optionsPresets 88 * @param {import('test/translator').OptionsList} optionsArray 89 * @returns {import('translation').FindTermsOptions} 90 */ 91export function createFindTermsOptions(dictionaryName, optionsPresets, optionsArray) { 92 const preset = getCompositePreset('terms', optionsPresets, optionsArray); 93 94 /** @type {import('translation').TermEnabledDictionaryMap} */ 95 const enabledDictionaryMap = new Map(); 96 const presetEnabledDictionaryMap = preset.enabledDictionaryMap; 97 if (Array.isArray(presetEnabledDictionaryMap)) { 98 for (const [key, value] of presetEnabledDictionaryMap) { 99 enabledDictionaryMap.set(key === placeholder ? dictionaryName : key, value); 100 } 101 } 102 103 /** @type {import('translation').FindTermsTextReplacements} */ 104 const textReplacements = []; 105 if (Array.isArray(preset.textReplacements)) { 106 for (const value of preset.textReplacements) { 107 if (Array.isArray(value)) { 108 const array = []; 109 for (const {pattern, flags, replacement} of value) { 110 array.push({pattern: new RegExp(pattern, flags), replacement}); 111 } 112 textReplacements.push(array); 113 } else { 114 // Null 115 textReplacements.push(value); 116 } 117 } 118 } 119 120 const { 121 matchType, 122 deinflect, 123 mainDictionary, 124 sortFrequencyDictionary, 125 sortFrequencyDictionaryOrder, 126 removeNonJapaneseCharacters, 127 primaryReading, 128 excludeDictionaryDefinitions, 129 searchResolution, 130 language, 131 } = preset; 132 133 return { 134 matchType: typeof matchType !== 'undefined' ? matchType : 'exact', 135 deinflect: typeof deinflect !== 'undefined' ? deinflect : true, 136 mainDictionary: typeof mainDictionary !== 'undefined' && mainDictionary !== placeholder ? mainDictionary : dictionaryName, 137 sortFrequencyDictionary: typeof sortFrequencyDictionary !== 'undefined' ? sortFrequencyDictionary : null, 138 sortFrequencyDictionaryOrder: typeof sortFrequencyDictionaryOrder !== 'undefined' ? sortFrequencyDictionaryOrder : 'ascending', 139 removeNonJapaneseCharacters: typeof removeNonJapaneseCharacters !== 'undefined' ? removeNonJapaneseCharacters : false, 140 primaryReading: typeof primaryReading !== 'undefined' ? primaryReading : '', 141 textReplacements, 142 enabledDictionaryMap, 143 excludeDictionaryDefinitions: Array.isArray(excludeDictionaryDefinitions) ? new Set(excludeDictionaryDefinitions) : null, 144 searchResolution: typeof searchResolution !== 'undefined' ? searchResolution : 'letter', 145 language: typeof language !== 'undefined' ? language : 'ja', 146 }; 147}