Skip to content

Commit

Permalink
#1439 pitch accents and pronunciations are grouped by pronunciation r…
Browse files Browse the repository at this point in the history
…ather than by dictionary. Now every pitch enthusiast can install 800+ dictionaries and the view will still be compact enough.
  • Loading branch information
ganqqwerty committed Nov 14, 2024
1 parent 746bed5 commit 1d5cc1c
Show file tree
Hide file tree
Showing 4 changed files with 414 additions and 190 deletions.
36 changes: 33 additions & 3 deletions ext/js/dictionary/dictionary-data-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,18 +346,18 @@ export function compareRevisions(current, latest) {
*/
function findExistingGroupedPronunciation(reading, pronunciation, groupedPronunciationList) {
const existingGroupedPronunciation = groupedPronunciationList.find((groupedPronunciation) => {
return groupedPronunciation.reading === reading && arePronunciationsEquivalent(groupedPronunciation, pronunciation);
return groupedPronunciation.reading === reading && arePronunciationsEquivalent(groupedPronunciation.pronunciation, pronunciation);
});

return existingGroupedPronunciation || null;
}

/**
* @param {import('dictionary-data-util').GroupedPronunciationInternal} groupedPronunciation
* @param {import('dictionary').Pronunciation} pronunciation1
* @param {import('dictionary').Pronunciation} pronunciation2
* @returns {boolean}
*/
function arePronunciationsEquivalent({pronunciation: pronunciation1}, pronunciation2) {
function arePronunciationsEquivalent(pronunciation1, pronunciation2) {
if (
pronunciation1.type !== pronunciation2.type ||
!areTagListsEqual(pronunciation1.tags, pronunciation2.tags)
Expand Down Expand Up @@ -463,3 +463,33 @@ function getSetIntersection(set1, set2) {
function createMapKey(array) {
return JSON.stringify(array);
}


/**
* @param {import('dictionary-data-util').DictionaryGroupedPronunciations[]} groupedPronunciations
* @returns {import('dictionary-data-util').PronunciationsInSeveralDictionaries[]}
*/
export function groupByPronunciation(groupedPronunciations) {
const groupedList = [];

for (const dictionaryGroup of groupedPronunciations) {
const {dictionary, pronunciations} = dictionaryGroup;

for (const pronunciation of pronunciations) {
const existingEntry = groupedList.find((entry) => arePronunciationsEquivalent(entry.pronunciation.pronunciation, pronunciation.pronunciation));

if (existingEntry) {
if (!existingEntry.dictionaries.includes(dictionary)) {
existingEntry.dictionaries.push(dictionary);
}
} else {
groupedList.push({
pronunciation,
dictionaries: [dictionary],
});
}
}
}

return groupedList;
}
138 changes: 94 additions & 44 deletions ext/js/display/display-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,24 @@
*/

import {ExtensionError} from '../core/extension-error.js';
import {getDisambiguations, getGroupedPronunciations, getTermFrequency, groupKanjiFrequencies, groupTermFrequencies, groupTermTags, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js';
import {
getDisambiguations,
getGroupedPronunciations,
getTermFrequency,
groupByPronunciation,
groupKanjiFrequencies,
groupTermFrequencies,
groupTermTags,
isNonNounVerbOrAdjective,
} from '../dictionary/dictionary-data-util.js';
import {HtmlTemplateCollection} from '../dom/html-template-collection.js';
import {distributeFurigana, getKanaMorae, getPitchCategory, isCodePointKanji} from '../language/ja/japanese.js';
import {getLanguageFromText} from '../language/text-utilities.js';
import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from './pronunciation-generator.js';
import {
createPronunciationDownstepPosition,
createPronunciationGraph,
createPronunciationText,
} from './pronunciation-generator.js';
import {StructuredContentGenerator} from './structured-content-generator.js';

export class DisplayGenerator {
Expand Down Expand Up @@ -58,7 +71,9 @@ export class DisplayGenerator {
/** */
updateHotkeys() {
const hotkeyHelpController = this._hotkeyHelpController;
if (hotkeyHelpController === null) { return; }
if (hotkeyHelpController === null) {
return;
}
for (const template of this._templates.getAllTemplates()) {
hotkeyHelpController.setupNode(template.content);
}
Expand All @@ -78,7 +93,14 @@ export class DisplayGenerator {
const definitionsContainer = this._querySelector(node, '.definition-list');
const headwordTagsContainer = this._querySelector(node, '.headword-list-tag-list');

const {headwords, type, inflectionRuleChainCandidates, definitions, frequencies, pronunciations} = dictionaryEntry;
const {
headwords,
type,
inflectionRuleChainCandidates,
definitions,
frequencies,
pronunciations,
} = dictionaryEntry;
const groupedPronunciations = getGroupedPronunciations(dictionaryEntry);
const pronunciationCount = groupedPronunciations.reduce((i, v) => i + v.pronunciations.length, 0);
const groupedFrequencies = groupTermFrequencies(dictionaryEntry);
Expand All @@ -94,7 +116,9 @@ export class DisplayGenerator {
uniqueTerms.add(term);
uniqueReadings.add(reading);
for (const {matchType, isPrimary} of sources) {
if (!isPrimary) { continue; }
if (!isPrimary) {
continue;
}
primaryMatchTypes.add(matchType);
}
}
Expand All @@ -119,14 +143,17 @@ export class DisplayGenerator {

this._appendMultiple(inflectionRuleChainsContainer, this._createInflectionRuleChain.bind(this), inflectionRuleChainCandidates);
this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, false);
this._appendMultiple(groupedPronunciationsContainer, this._createGroupedPronunciation.bind(this), groupedPronunciations);
const dictionariesGroupedByPronunciation = groupByPronunciation(groupedPronunciations);
this._appendMultiple(groupedPronunciationsContainer, this._createGroupedPronunciationByPronunciation.bind(this), dictionariesGroupedByPronunciation);
this._appendMultiple(headwordTagsContainer, this._createTermTag.bind(this), termTags, headwords.length);

for (const term of uniqueTerms) {
headwordTagsContainer.appendChild(this._createSearchTag(term));
}
for (const reading of uniqueReadings) {
if (uniqueTerms.has(reading)) { continue; }
if (uniqueTerms.has(reading)) {
continue;
}
headwordTagsContainer.appendChild(this._createSearchTag(reading));
}

Expand Down Expand Up @@ -173,7 +200,9 @@ export class DisplayGenerator {
const dictionaryIndicesContainer = this._querySelector(node, '.kanji-dictionary-indices');

this._setTextContent(glyphContainer, dictionaryEntry.character, this._language);
if (this._language === 'ja') { glyphContainer.style.fontFamily = 'kanji-stroke-orders, sans-serif'; }
if (this._language === 'ja') {
glyphContainer.style.fontFamily = 'kanji-stroke-orders, sans-serif';
}
const groupedFrequencies = groupKanjiFrequencies(dictionaryEntry.frequencies);

const dictionaryTag = this._createDictionaryTag('');
Expand Down Expand Up @@ -235,7 +264,9 @@ export class DisplayGenerator {
const copyAttributes = ['totalHeadwordCount', 'matchedHeadwordCount', 'unmatchedHeadwordCount'];
for (const attribute of copyAttributes) {
const value = tagNode.dataset[attribute];
if (typeof value === 'undefined') { continue; }
if (typeof value === 'undefined') {
continue;
}
disambiguationContainer.dataset[attribute] = value;
}
for (const {term, reading} of disambiguationHeadwords) {
Expand Down Expand Up @@ -275,7 +306,9 @@ export class DisplayGenerator {
const {referenceUrl} = /** @type {import('core').UnknownObject} */ (error.data);
if (typeof referenceUrl === 'string') {
message = message.trimEnd();
if (!/[.!?]^/.test(message)) { message += '.'; }
if (!/[.!?]^/.test(message)) {
message += '.';
}
message += ' ';
link = document.createElement('a');
link.href = referenceUrl;
Expand All @@ -285,7 +318,9 @@ export class DisplayGenerator {
}
}
this._setTextContent(div, message);
if (link !== null) { div.appendChild(link); }
if (link !== null) {
div.appendChild(link);
}
}
list.appendChild(div);
}
Expand Down Expand Up @@ -371,7 +406,9 @@ export class DisplayGenerator {
*/
_createInflectionRuleChain(inflectionRuleChain) {
const {source, inflectionRules} = inflectionRuleChain;
if (!Array.isArray(inflectionRules) || inflectionRules.length === 0) { return null; }
if (!Array.isArray(inflectionRules) || inflectionRules.length === 0) {
return null;
}
const fragment = this._instantiate('inflection-rule-chain');

const sourceIcon = this._getInflectionSourceIcon(source);
Expand Down Expand Up @@ -412,7 +449,9 @@ export class DisplayGenerator {
const fragment = this._templates.instantiateFragment('inflection');
const node = this._querySelector(fragment, '.inflection');
this._setTextContent(node, name);
if (description) { node.title = description; }
if (description) {
node.title = description;
}
node.dataset.reason = name;
return fragment;
}
Expand Down Expand Up @@ -613,7 +652,9 @@ export class DisplayGenerator {
this._setTextContent(inner, name);
node.dataset.details = contentString.length > 0 ? contentString : name;
node.dataset.category = category;
if (redundant) { node.dataset.redundant = 'true'; }
if (redundant) {
node.dataset.redundant = 'true';
}

return node;
}
Expand Down Expand Up @@ -659,37 +700,32 @@ export class DisplayGenerator {
}

/**
* @param {import('dictionary-data-util').DictionaryGroupedPronunciations} details
* @param {import('dictionary-data-util').PronunciationsInSeveralDictionaries} details
* @returns {HTMLElement}
*/
_createGroupedPronunciation(details) {
const {dictionary, dictionaryAlias, pronunciations} = details;
_createGroupedPronunciationByPronunciation({pronunciation, dictionaries}) {
const container = document.createElement('div');
container.classList.add('grouped-pronunciation-container');

const node = this._instantiate('pronunciation-group');
node.dataset.dictionary = dictionary;
node.dataset.pronunciationsMulti = 'true';
node.dataset.pronunciationsCount = `${pronunciations.length}`;

const n1 = this._querySelector(node, '.pronunciation-group-tag-list');
const tag = this._createTag(this._createTagData(dictionaryAlias, 'pronunciation-dictionary'));
tag.dataset.details = dictionary;
n1.appendChild(tag);

let hasTags = false;
for (const {pronunciation: {tags}} of pronunciations) {
if (tags.length > 0) {
hasTags = true;
break;
}
const tagListNode = this._querySelector(node, '.pronunciation-group-tag-list');
for (const dictionary of dictionaries) {
const tag = this._createTag(this._createTagData(dictionary, 'pronunciation-dictionary'));
tag.dataset.details = dictionary;
tagListNode.appendChild(tag);
}

const n = this._querySelector(node, '.pronunciation-list');
n.dataset.hasTags = `${hasTags}`;
this._appendMultiple(n, this._createPronunciation.bind(this), pronunciations);
const pronunciationListNode = this._querySelector(node, '.pronunciation-list');

return node;
this._appendMultiple(pronunciationListNode, this._createPronunciation.bind(this), [pronunciation]);

container.appendChild(node);

return container;
}


/**
* @param {import('dictionary-data-util').GroupedPronunciation} details
* @returns {HTMLElement}
Expand Down Expand Up @@ -746,8 +782,12 @@ export class DisplayGenerator {

node.dataset.pitchAccentDownstepPosition = `${position}`;
node.dataset.pronunciationType = pitchAccent.type;
if (nasalPositions.length > 0) { node.dataset.nasalMoraPosition = nasalPositions.join(' '); }
if (devoicePositions.length > 0) { node.dataset.devoiceMoraPosition = devoicePositions.join(' '); }
if (nasalPositions.length > 0) {
node.dataset.nasalMoraPosition = nasalPositions.join(' ');
}
if (devoicePositions.length > 0) {
node.dataset.devoiceMoraPosition = devoicePositions.join(' ');
}
node.dataset.tagCount = `${tags.length}`;

let n = this._querySelector(node, '.pronunciation-tag-list');
Expand Down Expand Up @@ -817,8 +857,8 @@ export class DisplayGenerator {
const item = frequencies[i];
const itemNode = (
kanji ?
this._createKanjiFrequency(/** @type {import('dictionary-data-util').KanjiFrequency} */ (item), dictionary, dictionaryAlias) :
this._createTermFrequency(/** @type {import('dictionary-data-util').TermFrequency} */ (item), dictionary, dictionaryAlias)
this._createKanjiFrequency(/** @type {import('dictionary-data-util').KanjiFrequency} */ (item), dictionary, dictionaryAlias) :
this._createTermFrequency(/** @type {import('dictionary-data-util').TermFrequency} */ (item), dictionary, dictionaryAlias)
);
itemNode.dataset.index = `${i}`;
body.appendChild(itemNode);
Expand Down Expand Up @@ -964,7 +1004,9 @@ export class DisplayGenerator {
if (Array.isArray(detailsArray)) {
for (const details of detailsArray) {
const item = createItem(details, /** @type {TExtraArg} */ (arg));
if (item === null) { continue; }
if (item === null) {
continue;
}
container.appendChild(item);
if (item.nodeType === ELEMENT_NODE) {
/** @type {HTMLElement} */ (item).dataset.index = `${count}`;
Expand Down Expand Up @@ -1032,7 +1074,9 @@ export class DisplayGenerator {
let start = 0;
while (true) {
const end = value.indexOf('\n', start);
if (end < 0) { break; }
if (end < 0) {
break;
}
node.appendChild(document.createTextNode(value.substring(start, end)));
node.appendChild(document.createElement('br'));
start = end + 1;
Expand Down Expand Up @@ -1067,14 +1111,20 @@ export class DisplayGenerator {
* @returns {?string}
*/
_getPronunciationCategories(reading, termPronunciations, wordClasses, headwordIndex) {
if (termPronunciations.length === 0) { return null; }
if (termPronunciations.length === 0) {
return null;
}
const isVerbOrAdjective = isNonNounVerbOrAdjective(wordClasses);
/** @type {Set<import('japanese-util').PitchCategory>} */
const categories = new Set();
for (const termPronunciation of termPronunciations) {
if (termPronunciation.headwordIndex !== headwordIndex) { continue; }
if (termPronunciation.headwordIndex !== headwordIndex) {
continue;
}
for (const pronunciation of termPronunciation.pronunciations) {
if (pronunciation.type !== 'pitch-accent') { continue; }
if (pronunciation.type !== 'pitch-accent') {
continue;
}
const category = getPitchCategory(reading, pronunciation.position, isVerbOrAdjective);
if (category !== null) {
categories.add(category);
Expand Down
Loading

0 comments on commit 1d5cc1c

Please sign in to comment.