Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
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}