fix: prioritize SubMiner definitions in Yomitan

This commit is contained in:
2026-03-06 22:01:20 -08:00
parent ca0eec568c
commit 1fd3f0575b
4 changed files with 123 additions and 3 deletions

View File

@@ -656,7 +656,7 @@ test('getYomitanDictionaryInfo requests dictionary info via backend action', asy
assert.match(scriptValue, /getDictionaryInfo/); assert.match(scriptValue, /getDictionaryInfo/);
}); });
test('dictionary settings helpers upsert and remove dictionary entries', async () => { test('dictionary settings helpers upsert and remove dictionary entries without reordering', async () => {
const scripts: string[] = []; const scripts: string[] = [];
const optionsFull = { const optionsFull = {
profileCurrent: 0, profileCurrent: 0,

View File

@@ -1676,7 +1676,7 @@ export async function upsertYomitanDictionarySettings(
continue; continue;
} }
dictionaries.unshift(createDefaultDictionarySettings(normalizedTitle, true)); dictionaries.push(createDefaultDictionarySettings(normalizedTitle, true));
changed = true; changed = true;
} }

View File

@@ -0,0 +1,93 @@
import assert from 'node:assert/strict';
import test from 'node:test';
// @ts-expect-error Vendored Yomitan translator has no local TypeScript declarations.
import { Translator } from '../vendor/yomitan/js/language/translator.js';
type SortableTermEntry = {
matchPrimaryReading: boolean;
maxOriginalTextLength: number;
textProcessorRuleChainCandidates: unknown[];
inflectionRuleChainCandidates: unknown[];
sourceTermExactMatchCount: number;
frequencyOrder: number;
dictionaryIndex: number;
score: number;
dictionaryAlias: string;
headwords: Array<{ term: string }>;
definitions: Array<{ dictionary: string }>;
};
type SortableDefinition = {
dictionary: string;
dictionaryAlias: string;
frequencyOrder: number;
dictionaryIndex: number;
score: number;
headwordIndices: number[];
index: number;
};
test('Translator prioritizes SubMiner term entries without changing dictionary index order', () => {
const translator = new Translator({});
const entries: SortableTermEntry[] = [
{
matchPrimaryReading: true,
maxOriginalTextLength: 4,
textProcessorRuleChainCandidates: [],
inflectionRuleChainCandidates: [],
sourceTermExactMatchCount: 1,
frequencyOrder: 0,
dictionaryIndex: 0,
score: 10,
dictionaryAlias: 'JMdict',
headwords: [{ term: 'アイリス' }],
definitions: [{ dictionary: 'JMdict' }],
},
{
matchPrimaryReading: true,
maxOriginalTextLength: 4,
textProcessorRuleChainCandidates: [],
inflectionRuleChainCandidates: [],
sourceTermExactMatchCount: 1,
frequencyOrder: 99,
dictionaryIndex: 99,
score: 1,
dictionaryAlias: 'SubMiner Character Dictionary',
headwords: [{ term: 'アイリス' }],
definitions: [{ dictionary: 'SubMiner Character Dictionary' }],
},
];
translator._sortTermDictionaryEntries(entries as unknown[]);
assert.equal(entries[0]?.dictionaryAlias, 'SubMiner Character Dictionary');
});
test('Translator prioritizes SubMiner definitions without changing dictionary index order', () => {
const translator = new Translator({});
const definitions: SortableDefinition[] = [
{
dictionary: 'JMdict',
dictionaryAlias: 'JMdict',
frequencyOrder: 0,
dictionaryIndex: 0,
score: 10,
headwordIndices: [0],
index: 0,
},
{
dictionary: 'SubMiner Character Dictionary',
dictionaryAlias: 'SubMiner Character Dictionary',
frequencyOrder: 99,
dictionaryIndex: 99,
score: 1,
headwordIndices: [0],
index: 1,
},
];
translator._sortTermDictionaryEntryDefinitions(definitions as unknown[]);
assert.equal(definitions[0]?.dictionaryAlias, 'SubMiner Character Dictionary');
});

View File

@@ -25,6 +25,8 @@ import {getAllLanguageReadingNormalizers, getAllLanguageTextProcessors} from './
import {MultiLanguageTransformer} from './multi-language-transformer.js'; import {MultiLanguageTransformer} from './multi-language-transformer.js';
import {isCodePointChinese} from './zh/chinese.js'; import {isCodePointChinese} from './zh/chinese.js';
const SUBMINER_DICTIONARY_TITLE_PREFIX = 'SubMiner Character Dictionary';
/** /**
* Class which finds term and kanji dictionary entries for text. * Class which finds term and kanji dictionary entries for text.
*/ */
@@ -1531,6 +1533,23 @@ export class Translator {
return Array.isArray(value) ? value : (typeof value === 'number' ? [value] : []); return Array.isArray(value) ? value : (typeof value === 'number' ? [value] : []);
} }
/**
* @param {string|undefined} value
* @returns {boolean}
*/
_isSubMinerDictionary(value) {
return typeof value === 'string' && value.startsWith(SUBMINER_DICTIONARY_TITLE_PREFIX);
}
/**
* @param {string|undefined} dictionary
* @param {string|undefined} dictionaryAlias
* @returns {number}
*/
_getSubMinerDictionarySortBoost(dictionary, dictionaryAlias) {
return (this._isSubMinerDictionary(dictionary) || this._isSubMinerDictionary(dictionaryAlias)) ? 1 : 0;
}
// Kanji data // Kanji data
/** /**
@@ -2162,6 +2181,10 @@ export class Translator {
i = v2.sourceTermExactMatchCount - v1.sourceTermExactMatchCount; i = v2.sourceTermExactMatchCount - v1.sourceTermExactMatchCount;
if (i !== 0) { return i; } if (i !== 0) { return i; }
// Prefer SubMiner character dictionary entries without changing user dictionary order.
i = this._getSubMinerDictionarySortBoost(v2.definitions[0]?.dictionary, v2.dictionaryAlias) - this._getSubMinerDictionarySortBoost(v1.definitions[0]?.dictionary, v1.dictionaryAlias);
if (i !== 0) { return i; }
// Sort by frequency order // Sort by frequency order
i = v1.frequencyOrder - v2.frequencyOrder; i = v1.frequencyOrder - v2.frequencyOrder;
if (i !== 0) { return i; } if (i !== 0) { return i; }
@@ -2205,8 +2228,12 @@ export class Translator {
* @returns {number} * @returns {number}
*/ */
const compareFunction = (v1, v2) => { const compareFunction = (v1, v2) => {
// Prefer SubMiner character dictionary definitions without changing user dictionary order.
let i = this._getSubMinerDictionarySortBoost(v2.dictionary, v2.dictionaryAlias) - this._getSubMinerDictionarySortBoost(v1.dictionary, v1.dictionaryAlias);
if (i !== 0) { return i; }
// Sort by frequency order // Sort by frequency order
let i = v1.frequencyOrder - v2.frequencyOrder; i = v1.frequencyOrder - v2.frequencyOrder;
if (i !== 0) { return i; } if (i !== 0) { return i; }
// Sort by dictionary order // Sort by dictionary order