From 57b053844052337164a9f9396711c5946d1551b0 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 24 Feb 2025 07:12:58 -0500 Subject: [PATCH 1/7] Add option to select type of fallback sound (#1839) * Add option to select type of fallback sound * Set to advanced only --- README.md | 2 +- .../audio/{button.mp3 => fallback-bloop.mp3} | Bin ext/data/audio/fallback-click.mp3 | Bin 0 -> 17735 bytes ext/data/schemas/options-schema.json | 9 +++--- ext/js/data/options-util.js | 12 ++++++++ ext/js/display/display-audio.js | 10 +++---- ext/js/media/audio-system.js | 26 ++++++++++++------ ext/settings.html | 24 ++++++++++------ test/options-util.test.js | 6 ++-- types/ext/settings.d.ts | 4 ++- 10 files changed, 63 insertions(+), 30 deletions(-) rename ext/data/audio/{button.mp3 => fallback-bloop.mp3} (100%) create mode 100644 ext/data/audio/fallback-click.mp3 diff --git a/README.md b/README.md index 4949308f15..3b29a2278c 100644 --- a/README.md +++ b/README.md @@ -128,4 +128,4 @@ Yomitan uses several third-party libraries to function. ## Attribution -`button.mp3` is provided by [UNIVERSFIELD](https://pixabay.com/sound-effects/error-8-206492/) and licensed under the [Pixabay Content License](https://pixabay.com/service/license-summary/). +`fallback-bloop.mp3` is provided by [UNIVERSFIELD](https://pixabay.com/sound-effects/error-8-206492/) and licensed under the [Pixabay Content License](https://pixabay.com/service/license-summary/). diff --git a/ext/data/audio/button.mp3 b/ext/data/audio/fallback-bloop.mp3 similarity index 100% rename from ext/data/audio/button.mp3 rename to ext/data/audio/fallback-bloop.mp3 diff --git a/ext/data/audio/fallback-click.mp3 b/ext/data/audio/fallback-click.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7a9728d1c840d84555adbbf3b7097e253672b304 GIT binary patch literal 17735 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∓UR5ZC zp$O?I;KTknVbcjGnZC%bYVnI7!tX6(BhbYb_BA zc-fioMt-fi`BLM;(M?2JxnEqzw&%TxXYPi)d@W4J#37Zibl=&DbKBbZ$O|%gICrIU zZdaSe_SF4&vhC*i#`R0jZ=dxS$S9Zbojfc{du`rrAiu7wIqK0r?FY~C5BQK#x5$E< z8o+_Z)~Yl#oHU&Ru=Ztqmbd30%eP^e$g8!SJl)adh5Biyv;BDy*R#@+wbNg`fk4aT zpF>ww-QVcy#?ix+`jEQmM|-PH0!As0DT*ff)y(^qD29WUL|0(P6)rz^_fKE$Uk0iT znmM}IHgzW}E$XLy-NB?zRi8bqZM>irUR`ttKeBjy;1Riz95CTZ2VL07L?<69m&BgM zZ7sH5J<7WfHCrwsNs}ww8FXc{#03&BwOEl<6M(QZOU7k88iTtCbC5DM8ZiBMayeF^ zB=UBJmMFUNH1L|_WR61cL(E2+MMF1MQ5R`JlC zo?cI^R_se!v4FGWH^nB|C|p)@17>8agZr?lN9~naj{_04_-Z?| zUhjSO_DVcx*+kgzTSuqKgyQi`y&@*S4eOCrKRuew_2Mb(MTUZZ;EryMt@7_J@5>)| z&CN}-ZU>B^xtzp1r7VdJVDxchcj~SX<8nkDgjdPk;J~DhaXI7 zNQ|uDxYugU zmP#%KlKI&dj-DTl1&hmUFetEW7U%SR_UX(=QJ~JU9z^atp9mI$?8V89?q!4FI^gtk zAis=}dVGi1@|fCJwd_^CPVkz_{+O*y2ohlFwZg{0xAXwcg*-;Hr=6?VXco_{H zkUPR__p-0-h1}Ek_J?uGDMoXYECG_`opA9S_}MQ4m41%6a3=CF4zVhQVN*PBSL6OQ z;@EuZ$W#SmfANyd@+7KP3B|^8>=DKj?od8v_DAO0diK8C@6B)V^kQmJmB|Jc_x07K zY{?o(w46#)ckz@EG^(;VJSOF1Xcr4HHDN}uv1_84ZZNrqox2s(0bYZasOz1-%=5TI z*vp@xc6@o~c;4J->z2@(eX*2QY>KywsH9I6AexkpN=_Z*y*I~a1Xhz27Z*)tXe5rk zO$5emzBN%}R#{*KxmR-&9Y@KO>O~p6R0PwDsl>eGvAEl}B-f!#7yCxsgbWdTar`ce zK}eqBQthX2;P7;9=mtb``6qzYiO_H1==A9yY3)@w$!9xr6o1uqRN1( zFOV8emnmxzahyPDVsUp*v?Fp(Zfr&DF3D4Tn;7jVbAEL~XU3fkLLGBqpy-+|(&O@+Pg0 z^=1;+P$_fl2q?`Yy0c7>UcT%sc;sH>S0hVUTE0?KY@~}z7vb8QIU5d}gui=#(% zb@hr1SYDQz$kkY5E@o!_Zmw80GQ6T)enB3O(9yU5Hw*s1mis>dZmQDxUu7hA|BN_7 z|2yRk{onZa_*a4drUHKf_;339x2k`sz+V9VrBHv<{+kN?1>nEw>))#Wr2>Be_?JTc zP5W;u@E3sprmugi`j-m)1>j!_^*8OmslZlG*f literal 0 HcmV?d00001 diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index c47f7063fd..227bc945ec 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -391,7 +391,7 @@ "enabled", "volume", "autoPlay", - "playFallbackSound", + "fallbackSoundType", "sources" ], "properties": { @@ -409,9 +409,10 @@ "type": "boolean", "default": false }, - "playFallbackSound": { - "type": "boolean", - "default": true + "fallbackSoundType": { + "type": "string", + "enum": ["none", "click", "bloop"], + "default": "click" }, "sources": { "type": "array", diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 6c2bb816cf..b4d6129940 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -571,6 +571,7 @@ export class OptionsUtil { this._updateVersion57, this._updateVersion58, this._updateVersion59, + this._updateVersion60, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1594,6 +1595,17 @@ export class OptionsUtil { } } + /** + * - Replaced audio.playFallbackSound with audio.fallbackSoundType + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion60(options) { + for (const profile of options.profiles) { + profile.options.audio.fallbackSoundType = profile.options.audio.playFallbackSound ? 'click' : 'none'; + delete profile.options.audio.playFallbackSound; + } + } + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index ef035857ce..87f5691370 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -36,8 +36,8 @@ export class DisplayAudio { this._playbackVolume = 1; /** @type {boolean} */ this._autoPlay = false; - /** @type {boolean} */ - this._playFallbackSound = true; + /** @type {import('settings').FallbackSoundType} */ + this._fallbackSoundType = 'none'; /** @type {?import('core').Timeout} */ this._autoPlayAudioTimer = null; /** @type {number} */ @@ -170,10 +170,10 @@ export class DisplayAudio { _onOptionsUpdated({options}) { const { general: {language}, - audio: {enabled, autoPlay, playFallbackSound, volume, sources}, + audio: {enabled, autoPlay, fallbackSoundType, volume, sources}, } = options; this._autoPlay = enabled && autoPlay; - this._playFallbackSound = playFallbackSound; + this._fallbackSoundType = fallbackSoundType; this._playbackVolume = Number.isFinite(volume) ? Math.max(0, Math.min(1, volume / 100)) : 1; /** @type {Set} */ @@ -454,7 +454,7 @@ export class DisplayAudio { const sourceIndex = sources.indexOf(source); title = `From source ${1 + sourceIndex}: ${source.name}`; } else { - audio = this._audioSystem.getFallbackAudio(this._playFallbackSound); + audio = this._audioSystem.getFallbackAudio(this._fallbackSoundType); title = 'Could not find audio'; } diff --git a/ext/js/media/audio-system.js b/ext/js/media/audio-system.js index 7a7e40bebd..0dfa2d20f9 100644 --- a/ext/js/media/audio-system.js +++ b/ext/js/media/audio-system.js @@ -27,8 +27,8 @@ export class AudioSystem extends EventDispatcher { super(); /** @type {?HTMLAudioElement} */ this._fallbackAudio = null; - /** @type {?boolean} */ - this._playFallbackSound = null; + /** @type {?import('settings').FallbackSoundType} */ + this._fallbackSoundType = null; } /** @@ -45,14 +45,24 @@ export class AudioSystem extends EventDispatcher { } /** - * @param {boolean} playFallbackSound + * @param {import('settings').FallbackSoundType} fallbackSoundType * @returns {HTMLAudioElement} */ - getFallbackAudio(playFallbackSound) { - if (this._fallbackAudio === null || this._playFallbackSound !== playFallbackSound) { - // audio handler expects audio url to always be present, empty string must be used instead of `new Audio()` - this._fallbackAudio = playFallbackSound ? new Audio('/data/audio/button.mp3') : new Audio(''); - this._playFallbackSound = playFallbackSound; + getFallbackAudio(fallbackSoundType) { + if (this._fallbackAudio === null || this._fallbackSoundType !== fallbackSoundType) { + this._fallbackSoundType = fallbackSoundType; + switch (fallbackSoundType) { + case 'click': + this._fallbackAudio = new Audio('/data/audio/fallback-click.mp3'); + break; + case 'bloop': + this._fallbackAudio = new Audio('/data/audio/fallback-bloop.mp3'); + break; + case 'none': + // audio handler expects audio url to always be present, empty string must be used instead of `new Audio()` + this._fallbackAudio = new Audio(''); + break; + } } return this._fallbackAudio; } diff --git a/ext/settings.html b/ext/settings.html index 924c230fe5..b6b3101545 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -1461,15 +1461,23 @@

Yomitan Settings

-
-
-
Enable audio fallback sound
-
Play sound when Yomitan fails to fetch audio.
-
-
- +
+
+
+
Audio fallback sound
+
+ The sound to play when Yomitan fails to fetch audio. +
+
+
+ +
-
+
Audio volume
diff --git a/test/options-util.test.js b/test/options-util.test.js index bc41089571..8505a27204 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -76,7 +76,7 @@ function createProfileOptionsTestData1() { sources: ['jpod101', 'text-to-speech', 'custom', 'jpod101-alternate'], volume: 100, autoPlay: false, - playFallbackSound: true, + fallbackSoundType: 'click', customSourceUrl: 'http://localhost/audio.mp3?term={expression}&reading={reading}', textToSpeechVoice: 'example-voice', }, @@ -337,7 +337,7 @@ function createProfileOptionsUpdatedTestData1() { ], volume: 100, autoPlay: false, - playFallbackSound: true, + fallbackSoundType: 'click', }, scanning: { selectText: true, @@ -680,7 +680,7 @@ function createOptionsUpdatedTestData1() { }, ], profileCurrent: 0, - version: 59, + version: 60, global: { database: { prefixWildcardsSupported: false, diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index 5ca7e62c39..f5111e629b 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -167,10 +167,12 @@ export type AudioOptions = { enabled: boolean; volume: number; autoPlay: boolean; - playFallbackSound: boolean; + fallbackSoundType: FallbackSoundType; sources: AudioSourceOptions[]; }; +export type FallbackSoundType = 'none' | 'click' | 'bloop'; + export type AudioSourceOptions = { type: AudioSourceType; url: string; From 4f109a981fc60f65252398891ca7cfbab2bf4b42 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 24 Feb 2025 07:14:23 -0500 Subject: [PATCH 2/7] Add support for omnibox search with Yomitan (#1822) * Add support for omnibox search with Yomitan * Change keyword to "yomi" * Catch errors on omnibox listener --- dev/data/manifest-variants.json | 3 +++ ext/js/background/backend.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 0dd2a22f6b..5b7e1bdb80 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -33,6 +33,9 @@ "service_worker": "sw.js", "type": "module" }, + "omnibox": { + "keyword": "yomi" + }, "content_scripts": [ { "run_at": "document_idle", diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index a533805c52..a9dbbc3e54 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1373,6 +1373,8 @@ export class Backend { this._setupContextMenu(options); + this._attachOmniboxListener(); + void this._accessibilityController.update(this._getOptionsFull(false)); this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}}); @@ -1404,6 +1406,18 @@ export class Backend { } } + /** */ + _attachOmniboxListener() { + try { + chrome.omnibox.onInputEntered.addListener((text) => { + const newURL = 'search.html?query=' + encodeURIComponent(text); + void chrome.tabs.create({url: newURL}); + }); + } catch (e) { + log.error(e); + } + } + /** * @param {boolean} useSchema * @returns {import('settings').Options} From 85af21316e4f95f03f745373bac188fa22564c7a Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:16:45 -0500 Subject: [PATCH 3/7] Add {sentence-furigana-plain} handlebar (#1834) * Initial implementation * Add handlebars update * Fix distribution of furigana when rendered * Deduplicate code * Add to docs and dropdown * Update tests * Consolidate textFurigana and textFuriganaPlain --- ...nki-field-templates-upgrade-v61.handlebars | 9 ++ .../default-anki-field-templates.handlebars | 10 ++ ext/js/data/anki-note-builder.js | 26 ++++- ext/js/data/anki-template-util.js | 2 + ext/js/data/options-util.js | 9 ++ .../template-renderer-media-provider.js | 13 ++- ext/templates-modals.html | 4 + test/data/anki-note-builder-test-results.json | 109 ++++++++++++++++++ test/options-util.test.js | 2 +- types/ext/anki-note-builder.d.ts | 2 + types/ext/anki-templates.d.ts | 3 +- 11 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 ext/data/templates/anki-field-templates-upgrade-v61.handlebars diff --git a/ext/data/templates/anki-field-templates-upgrade-v61.handlebars b/ext/data/templates/anki-field-templates-upgrade-v61.handlebars new file mode 100644 index 0000000000..781d9f6387 --- /dev/null +++ b/ext/data/templates/anki-field-templates-upgrade-v61.handlebars @@ -0,0 +1,9 @@ +{{#*inline "sentence-furigana-plain"}} + {{~#if definition.cloze~}} + {{~#if (hasMedia "textFuriganaPlain" definition.cloze.sentence)~}} + {{{getMedia "textFuriganaPlain" definition.cloze.sentence escape=false}}} + {{~else~}} + {{{definition.cloze.sentence}}} + {{~/if~}} + {{~/if~}} +{{/inline}} \ No newline at end of file diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index a065e3848c..8efe3df51e 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -472,4 +472,14 @@ {{~/if~}} {{/inline}} +{{#*inline "sentence-furigana-plain"}} + {{~#if definition.cloze~}} + {{~#if (hasMedia "textFuriganaPlain" definition.cloze.sentence)~}} + {{{getMedia "textFuriganaPlain" definition.cloze.sentence escape=false}}} + {{~else~}} + {{{definition.cloze.sentence}}} + {{~/if~}} + {{~/if~}} +{{/inline}} + {{~> (lookup . "marker") ~}} diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index d7dcbcbac2..a9e7d144ee 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -522,8 +522,9 @@ export class AnkiNoteBuilder { break; } if (data !== null) { - const value = this._createFuriganaHtml(data, readingMode); - results.push({text, readingMode, details: {value}}); + const valueHtml = this._createFuriganaHtml(data, readingMode); + const valuePlain = this._createFuriganaPlain(data, readingMode); + results.push({text, readingMode, detailsHtml: {value: valueHtml}, detailsPlain: {value: valuePlain}}); } } return results; @@ -551,6 +552,27 @@ export class AnkiNoteBuilder { return result; } + /** + * @param {import('api').ParseTextLine[]} data + * @param {?import('anki-templates').TextFuriganaReadingMode} readingMode + * @returns {string} + */ + _createFuriganaPlain(data, readingMode) { + let result = ''; + for (const term of data) { + for (const {text, reading} of term) { + if (reading.length > 0) { + const reading2 = this._convertReading(reading, readingMode); + result += ` ${text}[${reading2}]`; + } else { + result += text; + } + } + } + result = result.substring(1); + return result; + } + /** * @param {string} reading * @param {?import('anki-templates').TextFuriganaReadingMode} readingMode diff --git a/ext/js/data/anki-template-util.js b/ext/js/data/anki-template-util.js index 4c8127ae5f..94384a29f0 100644 --- a/ext/js/data/anki-template-util.js +++ b/ext/js/data/anki-template-util.js @@ -63,6 +63,7 @@ export function getStandardFieldMarkers(type) { 'popup-selection-text', 'sentence', 'sentence-furigana', + 'sentence-furigana-plain', 'tags', 'url', ]; @@ -91,6 +92,7 @@ export function getStandardFieldMarkers(type) { 'popup-selection-text', 'sentence', 'sentence-furigana', + 'sentence-furigana-plain', 'stroke-count', 'tags', 'url', diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index b4d6129940..50b94d2e04 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -572,6 +572,7 @@ export class OptionsUtil { this._updateVersion58, this._updateVersion59, this._updateVersion60, + this._updateVersion61, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1606,6 +1607,14 @@ export class OptionsUtil { } } + /** + * - Added sentence-furigana-plain handlebar + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion61(options) { + await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v61.handlebars'); + } + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/templates/template-renderer-media-provider.js b/ext/js/templates/template-renderer-media-provider.js index 52b40382dc..4d0258b462 100644 --- a/ext/js/templates/template-renderer-media-provider.js +++ b/ext/js/templates/template-renderer-media-provider.js @@ -102,7 +102,8 @@ export class TemplateRendererMediaProvider { case 'clipboardImage': return this._getSimpleMediaData(media, 'clipboardImage'); case 'clipboardText': return this._getSimpleMediaData(media, 'clipboardText'); case 'popupSelectionText': return this._getSimpleMediaData(media, 'popupSelectionText'); - case 'textFurigana': return this._getTextFurigana(media, args[1], namedArgs); + case 'textFurigana': return this._getTextFurigana(media, args[1], namedArgs, 'furiganaHtml'); + case 'textFuriganaPlain': return this._getTextFurigana(media, args[1], namedArgs, 'furiganaPlain'); case 'dictionaryMedia': return this._getDictionaryMedia(media, args[1], namedArgs); default: return null; } @@ -155,16 +156,22 @@ export class TemplateRendererMediaProvider { * @param {import('anki-templates').Media} media * @param {unknown} text * @param {import('core').SerializableObject} namedArgs + * @param {import('anki-note-builder').TextFuriganaFormats} furiganaFormat * @returns {?import('anki-templates').MediaObject} */ - _getTextFurigana(media, text, namedArgs) { + _getTextFurigana(media, text, namedArgs, furiganaFormat) { if (typeof text !== 'string') { return null; } const readingMode = this._normalizeReadingMode(namedArgs.readingMode); const {textFurigana} = media; if (Array.isArray(textFurigana)) { for (const entry of textFurigana) { if (entry.text !== text || entry.readingMode !== readingMode) { continue; } - return entry.details; + switch (furiganaFormat) { + case 'furiganaHtml': + return entry.detailsHtml; + case 'furiganaPlain': + return entry.detailsPlain; + } } } this._addRequirement({ diff --git a/ext/templates-modals.html b/ext/templates-modals.html index 04cf78793c..c19d262ddf 100644 --- a/ext/templates-modals.html +++ b/ext/templates-modals.html @@ -1122,6 +1122,10 @@

Pronunciation Dictionaries

{sentence-furigana} Sentence, quote, or phrase that the term or kanji appears in from the source content, with furigana added. + + {sentence-furigana-plain} + Sentence, quote, or phrase that the term or kanji appears in from the source content, with furigana added in brackets. + {url} Address of the web page in which the term or kanji appeared in. diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json index 06abe67ea9..b06a67e5d0 100644 --- a/test/data/anki-note-builder-test-results.json +++ b/test/data/anki-note-builder-test-results.json @@ -26,6 +26,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "stroke-count": "Stroke count: Unknown", "tags": "", "url": "url:" @@ -59,6 +60,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix込cloze-suffix", "sentence-furigana": "cloze-prefix込cloze-suffix", + "sentence-furigana-plain": "cloze-prefix込cloze-suffix", "stroke-count": "Stroke count: Unknown", "tags": "", "url": "url:" @@ -111,6 +113,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -153,6 +156,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -200,6 +204,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打つcloze-suffix", "sentence-furigana": "cloze-prefix打つcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打つcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -242,6 +247,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打つcloze-suffix", "sentence-furigana": "cloze-prefix打つcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打つcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -284,6 +290,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打つcloze-suffix", "sentence-furigana": "cloze-prefix打つcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打つcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -326,6 +333,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打つcloze-suffix", "sentence-furigana": "cloze-prefix打つcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打つcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -368,6 +376,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -410,6 +419,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -457,6 +467,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -499,6 +510,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -541,6 +553,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -583,6 +596,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -625,6 +639,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -667,6 +682,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -709,6 +725,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -751,6 +768,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -793,6 +811,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -835,6 +854,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -882,6 +902,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix画像cloze-suffix", "sentence-furigana": "cloze-prefix画像cloze-suffix", + "sentence-furigana-plain": "cloze-prefix画像cloze-suffix", "tags": "E1, P, n", "url": "url:" } @@ -929,6 +950,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixだcloze-suffix", "sentence-furigana": "cloze-prefixだcloze-suffix", + "sentence-furigana-plain": "cloze-prefixだcloze-suffix", "tags": "E1, n", "url": "url:" } @@ -976,6 +998,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixダースcloze-suffix", "sentence-furigana": "cloze-prefixダースcloze-suffix", + "sentence-furigana-plain": "cloze-prefixダースcloze-suffix", "tags": "E1, abbr, n", "url": "url:" }, @@ -1018,6 +1041,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixダcloze-suffix", "sentence-furigana": "cloze-prefixダcloze-suffix", + "sentence-furigana-plain": "cloze-prefixダcloze-suffix", "tags": "E1, n", "url": "url:" } @@ -1065,6 +1089,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうつcloze-suffix", "sentence-furigana": "cloze-prefixうつcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうつcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -1107,6 +1132,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうつcloze-suffix", "sentence-furigana": "cloze-prefixうつcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうつcloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -1154,6 +1180,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixぶつcloze-suffix", "sentence-furigana": "cloze-prefixぶつcloze-suffix", + "sentence-furigana-plain": "cloze-prefixぶつcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -1196,6 +1223,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixぶつcloze-suffix", "sentence-furigana": "cloze-prefixぶつcloze-suffix", + "sentence-furigana-plain": "cloze-prefixぶつcloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -1243,6 +1271,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうちこむcloze-suffix", "sentence-furigana": "cloze-prefixうちこむcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうちこむcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -1285,6 +1314,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうちこむcloze-suffix", "sentence-furigana": "cloze-prefixうちこむcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうちこむcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -1327,6 +1357,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうちcloze-suffix", "sentence-furigana": "cloze-prefixうちcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -1369,6 +1400,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうちcloze-suffix", "sentence-furigana": "cloze-prefixうちcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうちcloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -1416,6 +1448,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixぶちこむcloze-suffix", "sentence-furigana": "cloze-prefixぶちこむcloze-suffix", + "sentence-furigana-plain": "cloze-prefixぶちこむcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -1458,6 +1491,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixぶちこむcloze-suffix", "sentence-furigana": "cloze-prefixぶちこむcloze-suffix", + "sentence-furigana-plain": "cloze-prefixぶちこむcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -1500,6 +1534,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixぶちcloze-suffix", "sentence-furigana": "cloze-prefixぶちcloze-suffix", + "sentence-furigana-plain": "cloze-prefixぶちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -1542,6 +1577,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixぶちcloze-suffix", "sentence-furigana": "cloze-prefixぶちcloze-suffix", + "sentence-furigana-plain": "cloze-prefixぶちcloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -1589,6 +1625,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixがぞうcloze-suffix", "sentence-furigana": "cloze-prefixがぞうcloze-suffix", + "sentence-furigana-plain": "cloze-prefixがぞうcloze-suffix", "tags": "E1, P, n", "url": "url:" } @@ -1648,6 +1685,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "E1, E2, P, vt", "url": "url:" }, @@ -1690,6 +1728,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "E1, E2, P, vt", "url": "url:" }, @@ -1732,6 +1771,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E1, E2, P, vt", "url": "url:" }, @@ -1774,6 +1814,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E1, E2, P, vt", "url": "url:" }, @@ -1816,6 +1857,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -1858,6 +1900,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -1905,6 +1948,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込むcloze-suffix", "sentence-furigana": "cloze-prefix打ち込むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込むcloze-suffix", "tags": "vt", "url": "url:" }, @@ -1947,6 +1991,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "vt", "url": "url:" }, @@ -1989,6 +2034,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "n", "url": "url:" }, @@ -2031,6 +2077,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "abbr, n", "url": "url:" } @@ -2078,6 +2125,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "sentence-furigana": "cloze-prefix打ち込んでいませんでしたcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2120,6 +2168,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "sentence-furigana": "cloze-prefix打ち込んでいませんでしたcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2162,6 +2211,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "sentence-furigana": "cloze-prefix打ち込んでいませんでしたcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2204,6 +2254,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "sentence-furigana": "cloze-prefix打ち込んでいませんでしたcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ち込んでいませんでしたcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2246,6 +2297,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2288,6 +2340,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2330,6 +2383,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2372,6 +2426,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打ちcloze-suffix", "sentence-furigana": "cloze-prefix打ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打ちcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2414,6 +2469,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -2456,6 +2512,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打cloze-suffix", "sentence-furigana": "cloze-prefix打cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -2503,6 +2560,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ち込(こ)むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2545,6 +2603,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ち込(こ)むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2587,6 +2646,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ち込(こ)むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2629,6 +2689,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ち込(こ)むcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ち込(こ)むcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2671,6 +2732,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ちcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2713,6 +2775,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ちcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ちcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2755,6 +2818,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ちcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ちcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2797,6 +2861,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)ちcloze-suffix", "sentence-furigana": "cloze-prefix打(う)ちcloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)ちcloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -2839,6 +2904,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)cloze-suffix", "sentence-furigana": "cloze-prefix打(う)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -2881,6 +2947,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix打(う)cloze-suffix", "sentence-furigana": "cloze-prefix打(う)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix打(う)cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -2928,6 +2995,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -2970,6 +3038,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -3012,6 +3081,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -3054,6 +3124,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)(込)(む)cloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -3096,6 +3167,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)cloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -3138,6 +3210,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)cloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -3180,6 +3253,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)cloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -3222,6 +3296,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)(ち)cloze-suffix", "sentence-furigana": "cloze-prefix(打)(ち)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)(ち)cloze-suffix", "tags": "E2, P, vt", "url": "url:" }, @@ -3264,6 +3339,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)cloze-suffix", "sentence-furigana": "cloze-prefix(打)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)cloze-suffix", "tags": "E1, n", "url": "url:" }, @@ -3306,6 +3382,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix(打)cloze-suffix", "sentence-furigana": "cloze-prefix(打)cloze-suffix", + "sentence-furigana-plain": "cloze-prefix(打)cloze-suffix", "tags": "E1, abbr, n", "url": "url:" } @@ -3353,6 +3430,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixtestcloze-suffix", "sentence-furigana": "cloze-prefixtestcloze-suffix", + "sentence-furigana-plain": "cloze-prefixtestcloze-suffix", "tags": "E1, P, vt", "url": "url:" } @@ -3400,6 +3478,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixつtestcloze-suffix", "sentence-furigana": "cloze-prefixつtestcloze-suffix", + "sentence-furigana-plain": "cloze-prefixつtestcloze-suffix", "tags": "E1, P, n", "url": "url:" } @@ -3447,6 +3526,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixtestましたcloze-suffix", "sentence-furigana": "cloze-prefixtestましたcloze-suffix", + "sentence-furigana-plain": "cloze-prefixtestましたcloze-suffix", "tags": "E1, P, vt", "url": "url:" } @@ -3494,6 +3574,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうちこむcloze-suffix", "sentence-furigana": "cloze-prefixうちこむcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうちこむcloze-suffix", "tags": "vt", "url": "url:" }, @@ -3536,6 +3617,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixうちcloze-suffix", "sentence-furigana": "cloze-prefixうちcloze-suffix", + "sentence-furigana-plain": "cloze-prefixうちcloze-suffix", "tags": "vt", "url": "url:" } @@ -3583,6 +3665,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixお手前cloze-suffix", "sentence-furigana": "cloze-prefixお手前cloze-suffix", + "sentence-furigana-plain": "cloze-prefixお手前cloze-suffix", "tags": "n", "url": "url:" } @@ -3630,6 +3713,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix番号cloze-suffix", "sentence-furigana": "cloze-prefix番号cloze-suffix", + "sentence-furigana-plain": "cloze-prefix番号cloze-suffix", "tags": "n", "url": "url:" } @@ -3677,6 +3761,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix中腰cloze-suffix", "sentence-furigana": "cloze-prefix中腰cloze-suffix", + "sentence-furigana-plain": "cloze-prefix中腰cloze-suffix", "tags": "n", "url": "url:" } @@ -3724,6 +3809,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix所業cloze-suffix", "sentence-furigana": "cloze-prefix所業cloze-suffix", + "sentence-furigana-plain": "cloze-prefix所業cloze-suffix", "tags": "n", "url": "url:" } @@ -3771,6 +3857,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix土木工事cloze-suffix", "sentence-furigana": "cloze-prefix土木工事cloze-suffix", + "sentence-furigana-plain": "cloze-prefix土木工事cloze-suffix", "tags": "n", "url": "url:" } @@ -3818,6 +3905,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix好きcloze-suffix", "sentence-furigana": "cloze-prefix好きcloze-suffix", + "sentence-furigana-plain": "cloze-prefix好きcloze-suffix", "tags": "adj-na, n", "url": "url:" } @@ -3865,6 +3953,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix構造cloze-suffix", "sentence-furigana": "cloze-prefix構造cloze-suffix", + "sentence-furigana-plain": "cloze-prefix構造cloze-suffix", "tags": "E1, P, n", "url": "url:" } @@ -3912,6 +4001,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixのたもうたcloze-suffix", "sentence-furigana": "cloze-prefixのたもうたcloze-suffix", + "sentence-furigana-plain": "cloze-prefixのたもうたcloze-suffix", "tags": "v5", "url": "url:" } @@ -3959,6 +4049,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix39cloze-suffix", "sentence-furigana": "cloze-prefix39cloze-suffix", + "sentence-furigana-plain": "cloze-prefix39cloze-suffix", "tags": "", "url": "url:" } @@ -4006,6 +4097,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixEnglishcloze-suffix", "sentence-furigana": "cloze-prefixEnglishcloze-suffix", + "sentence-furigana-plain": "cloze-prefixEnglishcloze-suffix", "tags": "n", "url": "url:" } @@ -4053,6 +4145,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixUSBcloze-suffix", "sentence-furigana": "cloze-prefixUSBcloze-suffix", + "sentence-furigana-plain": "cloze-prefixUSBcloze-suffix", "tags": "n", "url": "url:" } @@ -4100,6 +4193,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixutsucloze-suffix", "sentence-furigana": "cloze-prefixutsucloze-suffix", + "sentence-furigana-plain": "cloze-prefixutsucloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -4142,6 +4236,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixutsucloze-suffix", "sentence-furigana": "cloze-prefixutsucloze-suffix", + "sentence-furigana-plain": "cloze-prefixutsucloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -4189,6 +4284,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixウツcloze-suffix", "sentence-furigana": "cloze-prefixウツcloze-suffix", + "sentence-furigana-plain": "cloze-prefixウツcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -4231,6 +4327,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixウツcloze-suffix", "sentence-furigana": "cloze-prefixウツcloze-suffix", + "sentence-furigana-plain": "cloze-prefixウツcloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -4278,6 +4375,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixてきすとcloze-suffix", "sentence-furigana": "cloze-prefixてきすとcloze-suffix", + "sentence-furigana-plain": "cloze-prefixてきすとcloze-suffix", "tags": "E1, P, n", "url": "url:" } @@ -4325,6 +4423,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixウツcloze-suffix", "sentence-furigana": "cloze-prefixウツcloze-suffix", + "sentence-furigana-plain": "cloze-prefixウツcloze-suffix", "tags": "E1, P, vt", "url": "url:" }, @@ -4367,6 +4466,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixウツcloze-suffix", "sentence-furigana": "cloze-prefixウツcloze-suffix", + "sentence-furigana-plain": "cloze-prefixウツcloze-suffix", "tags": "E2, P, vt", "url": "url:" } @@ -4414,6 +4514,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixすっっごーーいcloze-suffix", "sentence-furigana": "cloze-prefixすっっごーーいcloze-suffix", + "sentence-furigana-plain": "cloze-prefixすっっごーーいcloze-suffix", "tags": "adj-i", "url": "url:" } @@ -4461,6 +4562,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixenglishcloze-suffix", "sentence-furigana": "cloze-prefixenglishcloze-suffix", + "sentence-furigana-plain": "cloze-prefixenglishcloze-suffix", "tags": "n", "url": "url:" } @@ -4508,6 +4610,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixLANGUAGEcloze-suffix", "sentence-furigana": "cloze-prefixLANGUAGEcloze-suffix", + "sentence-furigana-plain": "cloze-prefixLANGUAGEcloze-suffix", "tags": "n", "url": "url:" } @@ -4555,6 +4658,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix마시거나cloze-suffix", "sentence-furigana": "cloze-prefix마시거나cloze-suffix", + "sentence-furigana-plain": "cloze-prefix마시거나cloze-suffix", "tags": "v", "url": "url:" } @@ -4602,6 +4706,7 @@ "popup-selection-text": "", "sentence": "cloze-prefixenglishcloze-suffix", "sentence-furigana": "cloze-prefixenglishcloze-suffix", + "sentence-furigana-plain": "cloze-prefixenglishcloze-suffix", "tags": "n", "url": "url:" } @@ -4649,6 +4754,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix自重cloze-suffix", "sentence-furigana": "cloze-prefix自重cloze-suffix", + "sentence-furigana-plain": "cloze-prefix自重cloze-suffix", "tags": "n", "url": "url:" }, @@ -4691,6 +4797,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix自重cloze-suffix", "sentence-furigana": "cloze-prefix自重cloze-suffix", + "sentence-furigana-plain": "cloze-prefix自重cloze-suffix", "tags": "n", "url": "url:" } @@ -4738,6 +4845,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix自重cloze-suffix", "sentence-furigana": "cloze-prefix自重cloze-suffix", + "sentence-furigana-plain": "cloze-prefix自重cloze-suffix", "tags": "n", "url": "url:" }, @@ -4780,6 +4888,7 @@ "popup-selection-text": "", "sentence": "cloze-prefix自重cloze-suffix", "sentence-furigana": "cloze-prefix自重cloze-suffix", + "sentence-furigana-plain": "cloze-prefix自重cloze-suffix", "tags": "n", "url": "url:" } diff --git a/test/options-util.test.js b/test/options-util.test.js index 8505a27204..08230c3b7b 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -680,7 +680,7 @@ function createOptionsUpdatedTestData1() { }, ], profileCurrent: 0, - version: 60, + version: 61, global: { database: { prefixWildcardsSupported: false, diff --git a/types/ext/anki-note-builder.d.ts b/types/ext/anki-note-builder.d.ts index e2a8d1af59..fcade7e34b 100644 --- a/types/ext/anki-note-builder.d.ts +++ b/types/ext/anki-note-builder.d.ts @@ -82,6 +82,8 @@ export type RequirementTextFurigana = { readingMode: AnkiTemplates.TextFuriganaReadingMode; }; +export type TextFuriganaFormats = 'furiganaHtml' | 'furiganaPlain'; + export type RequirementDictionaryMedia = { type: 'dictionaryMedia'; dictionary: string; diff --git a/types/ext/anki-templates.d.ts b/types/ext/anki-templates.d.ts index b9b864122b..c78edc7501 100644 --- a/types/ext/anki-templates.d.ts +++ b/types/ext/anki-templates.d.ts @@ -50,7 +50,8 @@ export type MediaSimpleType = ( export type TextFuriganaSegment = { text: string; readingMode: TextFuriganaReadingMode; - details: MediaObject; + detailsHtml: MediaObject; + detailsPlain: MediaObject; }; export type TextFuriganaReadingMode = 'hiragana' | 'katakana' | null; From 0357e8feb72fbd1e89581e5ccb623476ae483eb0 Mon Sep 17 00:00:00 2001 From: tadoru Date: Mon, 24 Feb 2025 12:31:57 -0500 Subject: [PATCH 4/7] add average frequency tag (#1479) * added average frequency tag * fix errors * add support for term grouping * added option for average frequency, hides other tags when active * combines 2 frequencies if one reading is "null" * updated option-util.test to include average frequency option * added single space before { * added average frequency tag * fix errors * add support for term grouping * added option for average frequency, hides other tags when active * combines 2 frequencies if one reading is "null" * updated option-util.test to include average frequency option * added single space before { * fixes typo, changes averages from object to map * reformat code, changes display.css to not depend on last-child to hide average frequency * uses single quotes instead of double quotes * fixed error that inverts the average frequency setting * Revert random formatting changes * Keep for loop const * Use null coalesce instead of if for reading check * Split out avg frequency array making * Set avg freq dict count to 1 * Simplify control flow * Split out avg frequency data creation * Clarify merging two readings if one is null --------- Co-authored-by: kuuuube --- ext/css/display.css | 9 +++ ext/data/schemas/options-schema.json | 5 ++ ext/js/dictionary/dictionary-data-util.js | 89 ++++++++++++++++++++++- ext/js/display/display.js | 1 + ext/settings.html | 9 +++ test/options-util.test.js | 2 + types/ext/dictionary.d.ts | 24 ++++++ types/ext/settings.d.ts | 1 + 8 files changed, 138 insertions(+), 2 deletions(-) diff --git a/ext/css/display.css b/ext/css/display.css index d76f202c87..c74f7e2aaf 100644 --- a/ext/css/display.css +++ b/ext/css/display.css @@ -1977,6 +1977,15 @@ button.footer-notification-close-button { :root[data-anki-enabled=false] .action-button[data-action=save-note] { display: none; } + +:root[data-average-frequency=true] .frequency-group-item:not([data-details='Average']) { + display: none; +} + +:root[data-average-frequency=false] .frequency-group-item[data-details='Average'] { + display: none; +} + :root[data-audio-enabled=false] .action-button[data-action=play-audio] { display: none; } diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 227bc945ec..c43b2b1c3c 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -102,6 +102,7 @@ "showGuide", "enableContextMenuScanSelected", "compactTags", + "averageFrequency", "glossaryLayoutMode", "mainDictionary", "popupTheme", @@ -236,6 +237,10 @@ "type": "boolean", "default": false }, + "averageFrequency": { + "type": "boolean", + "default": false + }, "glossaryLayoutMode": { "type": "string", "enum": ["default", "compact"], diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js index 61045a82cf..97dbb1026b 100644 --- a/ext/js/dictionary/dictionary-data-util.js +++ b/ext/js/dictionary/dictionary-data-util.js @@ -83,23 +83,108 @@ export function groupTermFrequencies(dictionaryEntry, dictionaryInfo) { } const results = []; + + /** @type {import('dictionary').AverageFrequencyListGroup} */ + const averages = new Map(); for (const [dictionary, map2] of map1.entries()) { + /** @type {import('dictionary-data-util').TermFrequency[]} */ const frequencies = []; const dictionaryAlias = aliasMap.get(dictionary) ?? dictionary; for (const {term, reading, values} of map2.values()) { - frequencies.push({ + const termFrequency = { term, reading, values: [...values.values()], - }); + }; + frequencies.push(termFrequency); + + const averageFrequencyData = makeAverageFrequencyData(termFrequency, averages.get(term)); + if (averageFrequencyData) { + averages.set(term, averageFrequencyData); + } } const currentDictionaryInfo = dictionaryInfo.find(({title}) => title === dictionary); const freqCount = currentDictionaryInfo?.counts?.termMeta.freq ?? 0; results.push({dictionary, frequencies, dictionaryAlias, freqCount}); } + + results.push({dictionary: 'Average', frequencies: makeAverageFrequencyArray(averages), dictionaryAlias: 'Average', freqCount: 1}); + return results; } +/** + * @param {import('dictionary-data-util').TermFrequency} termFrequency + * @param {import('dictionary').AverageFrequencyListTerm | undefined} averageTerm + * @returns {import('dictionary').AverageFrequencyListTerm | undefined} + */ +function makeAverageFrequencyData(termFrequency, averageTerm) { + const valuesArray = [...termFrequency.values.values()]; + const newReading = termFrequency.reading ?? ''; + + /** @type {import('dictionary').AverageFrequencyListTerm} */ + const termMap = typeof averageTerm === 'undefined' ? new Map() : averageTerm; + + const frequencyData = termMap.get(newReading) ?? {currentAvg: 1, count: 0}; + + if (valuesArray[0].frequency === null) { return; } + + frequencyData.currentAvg = frequencyData.count / frequencyData.currentAvg + 1 / valuesArray[0].frequency; + frequencyData.currentAvg = (frequencyData.count + 1) / frequencyData.currentAvg; + frequencyData.count += 1; + + termMap.set(newReading, frequencyData); + return termMap; +} + +/** + * @param {import('dictionary').AverageFrequencyListGroup} averages + * @returns {import('dictionary-data-util').TermFrequency[]} + */ +function makeAverageFrequencyArray(averages) { + // Merge readings if one is null and there's only two readings + // More than one non-null reading cannot be merged since it cannot be determined which reading to merge with + for (const currentTerm of averages.keys()) { + const readingsMap = averages.get(currentTerm); + if (!readingsMap) { continue; } // Skip if readingsMap is undefined + + const readingArray = [...readingsMap.keys()]; + const nullIndex = readingArray.indexOf(''); + + if (readingArray.length === 2 && nullIndex >= 0) { + const key1 = readingArray[0]; + const key2 = readingArray[1]; + + const value1 = readingsMap.get(key1); + const value2 = readingsMap.get(key2); + + if (!value1 || !value2) { continue; } // Skip if any value is undefined + + const avg1 = value1.currentAvg; + const count1 = value1.count; + const avg2 = value2.currentAvg; + const count2 = value2.count; + + const newcount = count1 + count2; + const newavg = newcount / (count1 / avg1 + count2 / avg2); + + const validKey = nullIndex === 0 ? key2 : key1; + readingsMap.set(validKey, {currentAvg: newavg, count: newcount}); + readingsMap.delete(''); + } + } + + // Convert averages Map back to array format + return [...averages.entries()].flatMap(([termName, termMap]) => [...termMap.entries()].map(([readingName, data]) => ({ + term: termName, + reading: readingName, + values: [{ + frequency: Math.round(data.currentAvg), + displayValue: Math.round(data.currentAvg).toString(), + }], + }))); +} + /** * @param {import('dictionary').KanjiFrequency[]} sourceFrequencies * @param {import('dictionary-importer').Summary[]} dictionaryInfo diff --git a/ext/js/display/display.js b/ext/js/display/display.js index d2437ef459..daf51a4ebc 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1202,6 +1202,7 @@ export class Display extends EventDispatcher { data.resultOutputMode = `${options.general.resultOutputMode}`; data.glossaryLayoutMode = `${options.general.glossaryLayoutMode}`; data.compactTags = `${options.general.compactTags}`; + data.averageFrequency = `${options.general.averageFrequency}`; data.frequencyDisplayMode = `${options.general.frequencyDisplayMode}`; data.termDisplayMode = `${options.general.termDisplayMode}`; data.enableSearchTags = `${options.scanning.enableSearchTags}`; diff --git a/ext/settings.html b/ext/settings.html index b6b3101545..e730f6658d 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -1090,6 +1090,15 @@

Yomitan Settings

+
+
+
Average frequencies
+
Compress frequency tags into one "Average" frequency tag based on the harmonic mean.
+
+
+ +
+
diff --git a/test/options-util.test.js b/test/options-util.test.js index 08230c3b7b..96a063dbf0 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -56,6 +56,7 @@ function createProfileOptionsTestData1() { popupScaleRelativeToVisualViewport: true, showGuide: true, compactTags: false, + averageFrequency: false, compactGlossaries: false, mainDictionary: '', popupTheme: 'default', @@ -288,6 +289,7 @@ function createProfileOptionsUpdatedTestData1() { showGuide: true, enableContextMenuScanSelected: true, compactTags: false, + averageFrequency: false, glossaryLayoutMode: 'default', mainDictionary: '', popupTheme: 'light', diff --git a/types/ext/dictionary.d.ts b/types/ext/dictionary.d.ts index 8d6a5d2a35..689a4fa787 100644 --- a/types/ext/dictionary.d.ts +++ b/types/ext/dictionary.d.ts @@ -532,3 +532,27 @@ export type TermSource = { */ isPrimary: boolean; }; + +/** + * Dictionaries containing the harmonic mean frequency of specific term-reading pairs. + */ +export type AverageFrequencyListGroup = Map; + +/** + * Contains the average frequency of a term, with all its readings. + */ +export type AverageFrequencyListTerm = Map; + +/** + * The number of dictionary frequencies used to compute the average. + */ +export type AverageFrequencyListReading = { + /** + * The current average frequency. + */ + currentAvg: number; + /** + * The number of dictionary frequencies used to compute the average. + */ + count: number; +}; diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index f5111e629b..08a1b8739c 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -128,6 +128,7 @@ export type GeneralOptions = { showGuide: boolean; enableContextMenuScanSelected: boolean; compactTags: boolean; + averageFrequency: boolean; glossaryLayoutMode: GlossaryLayoutMode; mainDictionary: string; popupTheme: PopupTheme; From 101eca0eed1d26aab44f5cff7de6c2108cc3ce55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Vukovi=C4=87?= Date: Mon, 24 Feb 2025 18:33:26 +0100 Subject: [PATCH 5/7] [grc] basic deinflection (#1818) * [grc] basic deinflections * fix copyright years * delete comment * more greek features * skipped file * lint * improve latin conversion * lint --- .../language/grc/ancient-greek-processors.js | 106 ++++++++++++++++ .../language/grc/ancient-greek-transforms.js | 120 ++++++++++++++++++ ext/js/language/language-descriptors.js | 4 + .../language/ancient-greek-processors.test.js | 30 +++++ .../language/ancient-greek-transforms.test.js | 61 +++++++++ types/ext/language-descriptors.d.ts | 4 +- 6 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 ext/js/language/grc/ancient-greek-processors.js create mode 100644 ext/js/language/grc/ancient-greek-transforms.js create mode 100644 test/language/ancient-greek-processors.test.js create mode 100644 test/language/ancient-greek-transforms.test.js diff --git a/ext/js/language/grc/ancient-greek-processors.js b/ext/js/language/grc/ancient-greek-processors.js new file mode 100644 index 0000000000..34e688cbfe --- /dev/null +++ b/ext/js/language/grc/ancient-greek-processors.js @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2025 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {basicTextProcessorOptions, removeAlphabeticDiacritics} from '../text-processors.js'; + +/** @type {import('language').TextProcessor} */ +export const convertLatinToGreek = { + name: 'Convert latin characters to greek', + description: 'a → α, A → Α, b → β, B → Β, etc.', + options: basicTextProcessorOptions, + process: (str, setting) => { + return setting ? latinToGreek(str) : str; + }, +}; + +/** + * @param {string} latin + * @returns {string} + */ +export function latinToGreek(latin) { + latin = removeAlphabeticDiacritics.process(latin, true); + + const singleMap = { + a: 'α', + b: 'β', + g: 'γ', + d: 'δ', + e: 'ε', + z: 'ζ', + ē: 'η', + i: 'ι', + k: 'κ', + l: 'λ', + m: 'μ', + n: 'ν', + x: 'ξ', + o: 'ο', + p: 'π', + r: 'ρ', + s: 'σ', + t: 'τ', + u: 'υ', + ō: 'ω', + A: 'Α', + B: 'Β', + G: 'Γ', + D: 'Δ', + E: 'Ε', + Z: 'Ζ', + Ē: 'Η', + I: 'Ι', + K: 'Κ', + L: 'Λ', + M: 'Μ', + N: 'Ν', + X: 'Ξ', + O: 'Ο', + P: 'Π', + R: 'Ρ', + S: 'Σ', + T: 'Τ', + U: 'Υ', + Ō: 'Ω', + }; + + const doubleMap = { + th: 'θ', + ph: 'φ', + ch: 'χ', + ps: 'ψ', + Th: 'Θ', + Ph: 'Φ', + Ch: 'Χ', + Ps: 'Ψ', + }; + + let result = latin; + + for (const [double, greek] of Object.entries(doubleMap)) { + result = result.replace(new RegExp(double, 'g'), greek); + } + + // Handle basic character replacements + for (const [single, greek] of Object.entries(singleMap)) { + result = result.replace(new RegExp(single, 'g'), greek); + } + + // Handle final sigma + result = result.replace(/σ$/, 'ς'); + + return result; +} diff --git a/ext/js/language/grc/ancient-greek-transforms.js b/ext/js/language/grc/ancient-greek-transforms.js new file mode 100644 index 0000000000..c9ccb99ff0 --- /dev/null +++ b/ext/js/language/grc/ancient-greek-transforms.js @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2025 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {suffixInflection} from '../language-transforms.js'; + +const conditions = { + v: { + name: 'Verb', + isDictionaryForm: true, + }, + n: { + name: 'Noun', + isDictionaryForm: true, + }, + adj: { + name: 'Adjective', + isDictionaryForm: true, + }, +}; + +/** @type {import('language-transformer').LanguageTransformDescriptor} */ +export const ancientGreekTransforms = { + language: 'grc', + conditions, + transforms: { + // inflections + // verbs + '3rd person singular present active indicative': { + name: '3rd person singular present active indicative', + rules: [ + suffixInflection('ει', 'ω', [], ['v']), + suffixInflection('ει', 'εω', [], ['v']), + ], + }, + + // nouns + 'accusative singular': { + name: 'accusative singular', + rules: [ + suffixInflection('ον', 'ος', [], ['n']), + ], + }, + 'genitive singular': { + name: 'genitive singular', + rules: [ + suffixInflection('ου', 'ος', [], ['n']), + ], + }, + 'dative singular': { + name: 'dative singular', + rules: [ + suffixInflection('ω', 'ος', [], ['n']), + ], + }, + 'vocative singular': { + name: 'vocative singular', + rules: [ + suffixInflection('ε', 'ος', [], ['n']), + ], + }, + 'nominative plural': { + name: 'nominative plural', + rules: [ + suffixInflection('οι', 'ος', [], ['n']), + ], + }, + 'genitive plural': { + name: 'genitive plural', + rules: [ + suffixInflection('ων', 'ος', [], ['n']), + ], + }, + 'dative plural': { + name: 'dative plural', + rules: [ + suffixInflection('οις', 'ος', [], ['n']), + ], + }, + 'accusative plural': { + name: 'accusative plural', + rules: [ + suffixInflection('ους', 'ος', [], ['n']), + ], + }, + 'vocative plural': { + name: 'vocative plural', + rules: [ + suffixInflection('οι', 'ος', [], ['n']), + ], + }, + // adjectives + 'accusative singular masculine': { + name: 'accusative singular masculine', + rules: [ + suffixInflection('ον', 'ος', [], ['adj']), + ], + }, + // word formation + 'nominalization': { + name: 'nominalization', + rules: [ + suffixInflection('ος', 'εω', [], ['v']), + ], + }, + }, +}; diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index 79d12f5ea8..164a2b8564 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -25,6 +25,8 @@ import {esperantoTransforms} from './eo/esperanto-transforms.js'; import {spanishTransforms} from './es/spanish-transforms.js'; import {apostropheVariants} from './fr/french-text-preprocessors.js'; import {frenchTransforms} from './fr/french-transforms.js'; +import {convertLatinToGreek} from './grc/ancient-greek-processors.js'; +import {ancientGreekTransforms} from './grc/ancient-greek-transforms.js'; import { alphabeticToHiragana, alphanumericWidthVariants, @@ -177,7 +179,9 @@ const languageDescriptors = [ textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, + convertLatinToGreek, }, + languageTransforms: ancientGreekTransforms, }, { iso: 'hi', diff --git a/test/language/ancient-greek-processors.test.js b/test/language/ancient-greek-processors.test.js new file mode 100644 index 0000000000..d76e942348 --- /dev/null +++ b/test/language/ancient-greek-processors.test.js @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2025 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {describe, expect, test} from 'vitest'; +import {latinToGreek} from '../../ext/js/language/grc/ancient-greek-processors.js'; + + +const testCases = [ + ['Zeus', 'Ζευς'], +]; + +describe('diacritics normalization', () => { + test.each(testCases)('%s converts to %s', (input, expected) => { + expect(latinToGreek(input)).toStrictEqual(expected); + }); +}); diff --git a/test/language/ancient-greek-transforms.test.js b/test/language/ancient-greek-transforms.test.js new file mode 100644 index 0000000000..ecc8add44a --- /dev/null +++ b/test/language/ancient-greek-transforms.test.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2025 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {ancientGreekTransforms} from '../../ext/js/language/grc/ancient-greek-transforms.js'; +import {LanguageTransformer} from '../../ext/js/language/language-transformer.js'; +import {testLanguageTransformer} from '../fixtures/language-transformer-test.js'; + +/* eslint-disable @stylistic/no-multi-spaces */ +const tests = [ + { + category: 'verbs', + valid: true, + tests: [ + {term: 'λύω', source: 'λύει', rule: 'v', reasons: ['3rd person singular present active indicative']}, + {term: 'φιλεω', source: 'φιλει', rule: 'v', reasons: ['3rd person singular present active indicative']}, + {term: 'γεωργεω', source: 'γεωργος', rule: 'v', reasons: ['nominalization']}, + ], + }, + { + category: 'nouns', + valid: true, + tests: [ + {term: 'ανθρωπος', source: 'ανθρωπον', rule: 'n', reasons: ['accusative singular']}, + {term: 'ανθρωπος', source: 'ανθρωπου', rule: 'n', reasons: ['genitive singular']}, + {term: 'ανθρωπος', source: 'ανθρωπε', rule: 'n', reasons: ['vocative singular']}, + {term: 'ανθρωπος', source: 'ανθρωπω', rule: 'n', reasons: ['dative singular']}, + {term: 'ανθρωπος', source: 'ανθρωποι', rule: 'n', reasons: ['nominative plural']}, + {term: 'ανθρωπος', source: 'ανθρωποις', rule: 'n', reasons: ['dative plural']}, + {term: 'ανθρωπος', source: 'ανθρωπους', rule: 'n', reasons: ['accusative plural']}, + {term: 'ανθρωπος', source: 'ανθρωπων', rule: 'n', reasons: ['genitive plural']}, + {term: 'ανθρωπος', source: 'ανθρωποι', rule: 'n', reasons: ['vocative plural']}, + ], + }, + { + category: 'adjectives', + valid: true, + tests: [ + {term: 'καλος', source: 'καλον', rule: 'adj', reasons: ['accusative singular masculine']}, + ], + }, +]; +/* eslint-enable @stylistic/no-multi-spaces */ + +const languageTransformer = new LanguageTransformer(); +languageTransformer.addDescriptor(ancientGreekTransforms); + +testLanguageTransformer(languageTransformer, tests); diff --git a/types/ext/language-descriptors.d.ts b/types/ext/language-descriptors.d.ts index 70cedb3e0d..b39bb82047 100644 --- a/types/ext/language-descriptors.d.ts +++ b/types/ext/language-descriptors.d.ts @@ -121,7 +121,9 @@ type AllTextProcessors = { }; }; grc: { - pre: CapitalizationPreprocessors & AlphabeticDiacriticsProcessor; + pre: CapitalizationPreprocessors & AlphabeticDiacriticsProcessor & { + convertLatinToGreek: TextProcessor; + }; }; hi: Record; hu: { From 7ba6c1321f6ec4b9fab0f9eeb8dd828e12fcc21c Mon Sep 17 00:00:00 2001 From: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:29:34 -0700 Subject: [PATCH 6/7] Normalize fallback-bloop.mp3 audio (#1841) --- ext/data/audio/fallback-bloop.mp3 | Bin 32600 -> 13355 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ext/data/audio/fallback-bloop.mp3 b/ext/data/audio/fallback-bloop.mp3 index 1b6926791502484bd11f49c5bacf6691efd03b51..e3d948736f929f4b31a65d2d0d7553a2f2383f96 100644 GIT binary patch literal 13355 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≧`c`qMtV{x*& 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 Date: Mon, 24 Feb 2025 20:27:47 -0500 Subject: [PATCH 7/7] edited options-util.js to include settings for average frequency, bumped version (#1842) --- ext/js/data/options-util.js | 11 +++++++++++ test/options-util.test.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 50b94d2e04..7412eb9023 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -573,6 +573,7 @@ export class OptionsUtil { this._updateVersion59, this._updateVersion60, this._updateVersion61, + this._updateVersion62, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1615,6 +1616,16 @@ export class OptionsUtil { await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v61.handlebars'); } + /** + * - Added options.general.averageFrequency + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion62(options) { + for (const profile of options.profiles) { + profile.options.general.averageFrequency = false; + } + } + /** * @param {string} url * @returns {Promise} diff --git a/test/options-util.test.js b/test/options-util.test.js index 96a063dbf0..5055f27de6 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -682,7 +682,7 @@ function createOptionsUpdatedTestData1() { }, ], profileCurrent: 0, - version: 61, + version: 62, global: { database: { prefixWildcardsSupported: false,