diff --git a/.eslintrc.json b/.eslintrc.json
index af9f22b469..68bc0795df 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -670,7 +670,7 @@
"ext/js/general/text-source-map.js",
"ext/js/language/deinflector.js",
"ext/js/dictionary/dictionary-database.js",
- "ext/js/language/sandbox/dictionary-data-util.js",
+ "ext/js/dictionary/dictionary-data-util.js",
"ext/js/language/sandbox/japanese-util.js",
"ext/js/language/translator.js",
"ext/js/media/audio-downloader.js",
diff --git a/dev/jsconfig.json b/dev/jsconfig.json
index a754006890..c791b5c0d2 100644
--- a/dev/jsconfig.json
+++ b/dev/jsconfig.json
@@ -66,7 +66,7 @@
"../ext/js/language/deinflector.js",
"../ext/js/dictionary/dictionary-importer.js",
"../ext/js/dictionary/dictionary-database.js",
- "../ext/js/language/sandbox/dictionary-data-util.js",
+ "../ext/js/dictionary/dictionary-data-util.js",
"../ext/js/language/sandbox/japanese-util.js",
"../ext/js/language/translator.js",
"../ext/js/media/media-util.js",
diff --git a/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json b/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json
index 995c456aff..89709a9ea8 100644
--- a/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json
+++ b/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json
@@ -40,8 +40,8 @@
},
{
"type": "string",
- "enum": ["freq", "pitch"],
- "description": "Type of data. \"freq\" corresponds to frequency information; \"pitch\" corresponds to pitch information."
+ "enum": ["freq", "pitch", "ipa"],
+ "description": "Type of data. \"freq\" corresponds to frequency information; \"pitch\" corresponds to pitch information. \"ipa\" corresponds to IPA transcription."
},
{
"description": "Data for the term."
@@ -61,7 +61,7 @@
"description": "Frequency information for the term."
},
{
- "type": ["object"],
+ "type": "object",
"required": [
"reading",
"frequency"
@@ -89,7 +89,7 @@
{},
{"const": "pitch"},
{
- "type": ["object"],
+ "type": "object",
"description": "Pitch accent information for the term.",
"required": [
"reading",
@@ -164,6 +164,54 @@
}
}
]
+ },
+ {
+ "minItems": 3,
+ "maxItems": 3,
+ "items": [
+ {},
+ {"const": "ipa"},
+ {
+ "type": ["object"],
+ "description": "IPA transcription information for the term.",
+ "required": [
+ "reading",
+ "transcriptions"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "reading": {
+ "type": "string",
+ "description": "Reading for the term."
+ },
+ "transcriptions": {
+ "type": "array",
+ "description": "List of different IPA transcription information for the term and reading combination.",
+ "items": {
+ "type": "object",
+ "required": [
+ "ipa"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "ipa": {
+ "type": "string",
+ "description": "IPA transcription for the term."
+ },
+ "tags": {
+ "type": "array",
+ "description": "List of tags for this IPA transcription.",
+ "items": {
+ "type": "string",
+ "description": "Tag for this IPA transcription."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
}
]
}
diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars
index d94f6d709f..f23b9d0be9 100644
--- a/ext/data/templates/default-anki-field-templates.handlebars
+++ b/ext/data/templates/default-anki-field-templates.handlebars
@@ -229,6 +229,27 @@
{{/inline}}
{{! End Pitch Accents }}
+{{#*inline "phonetic-transcriptions"}}
+ {{~#if (op ">" definition.phoneticTranscriptions.length 0)~}}
+
+ {{~#each definition.phoneticTranscriptions~}}
+ {{~#each phoneticTranscriptions~}}
+ -
+ {{~set "any" false~}}
+ {{~#each tags~}}
+ {{~#if (get "any")}}, {{else}}({{/if~}}
+ {{name}}
+ {{~set "any" true~}}
+ {{~/each~}}
+ {{~#if (get "any")}}) {{/if~}}
+ {{ipa~}}
+
+ {{~/each~}}
+ {{~/each~}}
+
+ {{~/if~}}
+{{/inline}}
+
{{#*inline "clipboard-image"}}
{{~#if (hasMedia "clipboardImage")~}}
diff --git a/ext/js/accessibility/accessibility-controller.js b/ext/js/accessibility/accessibility-controller.js
index 8250b36945..2b352948e8 100644
--- a/ext/js/accessibility/accessibility-controller.js
+++ b/ext/js/accessibility/accessibility-controller.js
@@ -16,19 +16,14 @@
* along with this program. If not, see .
*/
+import {isContentScriptRegistered, registerContentScript, unregisterContentScript} from '../background/script-manager.js';
import {log} from '../core.js';
/**
* This class controls the registration of accessibility handlers.
*/
export class AccessibilityController {
- /**
- * Creates a new instance.
- * @param {import('../background/script-manager.js').ScriptManager} scriptManager An instance of the `ScriptManager` class.
- */
- constructor(scriptManager) {
- /** @type {import('../background/script-manager.js').ScriptManager} */
- this._scriptManager = scriptManager;
+ constructor() {
/** @type {?import('core').TokenObject} */
this._updateGoogleDocsAccessibilityToken = null;
/** @type {?Promise} */
@@ -90,19 +85,17 @@ export class AccessibilityController {
const id = 'googleDocsAccessibility';
try {
if (forceGoogleDocsHtmlRenderingAny) {
- if (await this._scriptManager.isContentScriptRegistered(id)) { return; }
+ if (await isContentScriptRegistered(id)) { return; }
/** @type {import('script-manager').RegistrationDetails} */
const details = {
allFrames: true,
- matchAboutBlank: true,
matches: ['*://docs.google.com/*'],
- urlMatches: '^[^:]*://docs\\.google\\.com/[\\w\\W]*$',
runAt: 'document_start',
js: ['js/accessibility/google-docs.js']
};
- await this._scriptManager.registerContentScript(id, details);
+ await registerContentScript(id, details);
} else {
- await this._scriptManager.unregisterContentScript(id);
+ await unregisterContentScript(id);
}
} catch (e) {
log.error(e);
diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js
index b68b55f340..b093ec33fe 100644
--- a/ext/js/app/frontend.js
+++ b/ext/js/app/frontend.js
@@ -16,7 +16,8 @@
* along with this program. If not, see .
*/
-import {EventListenerCollection, invokeMessageHandler, log, promiseAnimationFrame} from '../core.js';
+import {EventListenerCollection, log, promiseAnimationFrame} from '../core.js';
+import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';
import {DocumentUtil} from '../dom/document-util.js';
import {TextSourceElement} from '../dom/text-source-element.js';
import {TextSourceRange} from '../dom/text-source-range.js';
@@ -106,12 +107,12 @@ export class Frontend {
this._optionsContextOverride = null;
/* eslint-disable no-multi-spaces */
- /** @type {import('core').MessageHandlerMap} */
- this._runtimeMessageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([
- ['Frontend.requestReadyBroadcast', this._onMessageRequestFrontendReadyBroadcast.bind(this)],
- ['Frontend.setAllVisibleOverride', this._onApiSetAllVisibleOverride.bind(this)],
- ['Frontend.clearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)]
- ]));
+ /** @type {import('application').ApiMap} */
+ this._runtimeApiMap = createApiMap([
+ ['frontendRequestReadyBroadcast', this._onMessageRequestFrontendReadyBroadcast.bind(this)],
+ ['frontendSetAllVisibleOverride', this._onApiSetAllVisibleOverride.bind(this)],
+ ['frontendClearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)]
+ ]);
this._hotkeyHandler.registerActions([
['scanSelectedText', this._onActionScanSelectedText.bind(this)],
@@ -239,9 +240,7 @@ export class Frontend {
// Message handlers
- /**
- * @param {import('frontend').FrontendRequestReadyBroadcastParams} params
- */
+ /** @type {import('application').ApiHandler<'frontendRequestReadyBroadcast'>} */
_onMessageRequestFrontendReadyBroadcast({frameId}) {
this._signalFrontendReady(frameId);
}
@@ -313,10 +312,7 @@ export class Frontend {
};
}
- /**
- * @param {{value: boolean, priority: number, awaitFrame: boolean}} params
- * @returns {Promise}
- */
+ /** @type {import('application').ApiHandler<'frontendSetAllVisibleOverride'>} */
async _onApiSetAllVisibleOverride({value, priority, awaitFrame}) {
const result = await this._popupFactory.setAllVisibleOverride(value, priority);
if (awaitFrame) {
@@ -325,10 +321,7 @@ export class Frontend {
return result;
}
- /**
- * @param {{token: import('core').TokenString}} params
- * @returns {Promise}
- */
+ /** @type {import('application').ApiHandler<'frontendClearAllVisibleOverride'>} */
async _onApiClearAllVisibleOverride({token}) {
return await this._popupFactory.clearAllVisibleOverride(token);
}
@@ -342,11 +335,9 @@ export class Frontend {
this._updatePopupPosition();
}
- /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
- _onRuntimeMessage({action, params}, sender, callback) {
- const messageHandler = this._runtimeMessageHandlers.get(action);
- if (typeof messageHandler === 'undefined') { return false; }
- return invokeMessageHandler(messageHandler, params, callback, sender);
+ /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
+ _onRuntimeMessage({action, params}, _sender, callback) {
+ return invokeApiMapHandler(this._runtimeApiMap, action, params, [], callback);
}
/**
@@ -827,12 +818,12 @@ export class Frontend {
* @param {?number} targetFrameId
*/
_signalFrontendReady(targetFrameId) {
- /** @type {import('frontend').FrontendReadyDetails} */
- const params = {frameId: this._frameId};
+ /** @type {import('application').ApiMessageNoFrameId<'frontendReady'>} */
+ const message = {action: 'frontendReady', params: {frameId: this._frameId}};
if (targetFrameId === null) {
- yomitan.api.broadcastTab('frontendReady', params);
+ yomitan.api.broadcastTab(message);
} else {
- yomitan.api.sendMessageToFrame(targetFrameId, 'frontendReady', params);
+ yomitan.api.sendMessageToFrame(targetFrameId, message);
}
}
@@ -853,11 +844,11 @@ export class Frontend {
}
chrome.runtime.onMessage.removeListener(onMessage);
};
- /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
+ /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
const onMessage = (message, _sender, sendResponse) => {
try {
- const {action, params} = message;
- if (action === 'frontendReady' && /** @type {import('frontend').FrontendReadyDetails} */ (params).frameId === frameId) {
+ const {action} = message;
+ if (action === 'frontendReady' && message.params.frameId === frameId) {
cleanup();
resolve();
sendResponse();
@@ -876,7 +867,7 @@ export class Frontend {
}
chrome.runtime.onMessage.addListener(onMessage);
- yomitan.api.broadcastTab('Frontend.requestReadyBroadcast', {frameId: this._frameId});
+ yomitan.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}});
});
}
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index b4be6c174d..68d4e0c89a 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -40,7 +40,7 @@ import {MediaUtil} from '../media/media-util.js';
import {ClipboardReaderProxy, DictionaryDatabaseProxy, OffscreenProxy, TranslatorProxy} from './offscreen-proxy.js';
import {ProfileConditionsUtil} from './profile-conditions-util.js';
import {RequestBuilder} from './request-builder.js';
-import {ScriptManager} from './script-manager.js';
+import {injectStylesheet} from './script-manager.js';
/**
* This class controls the core logic of the extension, including API calls
@@ -110,10 +110,8 @@ export class Backend {
});
/** @type {OptionsUtil} */
this._optionsUtil = new OptionsUtil();
- /** @type {ScriptManager} */
- this._scriptManager = new ScriptManager();
/** @type {AccessibilityController} */
- this._accessibilityController = new AccessibilityController(this._scriptManager);
+ this._accessibilityController = new AccessibilityController();
/** @type {?number} */
this._searchPopupTabId = null;
@@ -301,8 +299,8 @@ export class Backend {
this._clipboardMonitor.on('change', this._onClipboardTextChange.bind(this));
- this._sendMessageAllTabsIgnoreResponse('Yomitan.backendReady', {});
- this._sendMessageIgnoreResponse({action: 'Yomitan.backendReady', params: {}});
+ this._sendMessageAllTabsIgnoreResponse({action: 'applicationBackendReady'});
+ this._sendMessageIgnoreResponse({action: 'applicationBackendReady'});
} catch (e) {
log.error(e);
throw e;
@@ -406,7 +404,7 @@ export class Backend {
* @param {chrome.tabs.ZoomChangeInfo} event
*/
_onZoomChange({tabId, oldZoomFactor, newZoomFactor}) {
- this._sendMessageTabIgnoreResponse(tabId, {action: 'Yomitan.zoomChanged', params: {oldZoomFactor, newZoomFactor}}, {});
+ this._sendMessageTabIgnoreResponse(tabId, {action: 'applicationZoomChanged', params: {oldZoomFactor, newZoomFactor}}, {});
}
/**
@@ -429,7 +427,8 @@ export class Backend {
/** @type {import('api').ApiHandler<'requestBackendReadySignal'>} */
_onApiRequestBackendReadySignal(_params, sender) {
// tab ID isn't set in background (e.g. browser_action)
- const data = {action: 'Yomitan.backendReady', params: {}};
+ /** @type {import('application').ApiMessage<'applicationBackendReady'>} */
+ const data = {action: 'applicationBackendReady'};
if (typeof sender.tab === 'undefined') {
this._sendMessageIgnoreResponse(data);
return false;
@@ -611,30 +610,30 @@ export class Backend {
}
/** @type {import('api').ApiHandler<'sendMessageToFrame'>} */
- _onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) {
+ _onApiSendMessageToFrame({frameId: targetFrameId, message}, sender) {
if (!sender) { return false; }
const {tab} = sender;
if (!tab) { return false; }
const {id} = tab;
if (typeof id !== 'number') { return false; }
- const frameId = sender.frameId;
- /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */
- const message = {action, params, frameId};
- this._sendMessageTabIgnoreResponse(id, message, {frameId: targetFrameId});
+ const {frameId} = sender;
+ /** @type {import('application').ApiMessageAny} */
+ const message2 = {...message, frameId};
+ this._sendMessageTabIgnoreResponse(id, message2, {frameId: targetFrameId});
return true;
}
/** @type {import('api').ApiHandler<'broadcastTab'>} */
- _onApiBroadcastTab({action, params}, sender) {
+ _onApiBroadcastTab({message}, sender) {
if (!sender) { return false; }
const {tab} = sender;
if (!tab) { return false; }
const {id} = tab;
if (typeof id !== 'number') { return false; }
- const frameId = sender.frameId;
- /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */
- const message = {action, params, frameId};
- this._sendMessageTabIgnoreResponse(id, message, {});
+ const {frameId} = sender;
+ /** @type {import('application').ApiMessageAny} */
+ const message2 = {...message, frameId};
+ this._sendMessageTabIgnoreResponse(id, message2, {});
return true;
}
@@ -650,7 +649,7 @@ export class Backend {
async _onApiInjectStylesheet({type, value}, sender) {
const {frameId, tab} = sender;
if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); }
- return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false);
+ return await injectStylesheet(type, value, tab.id, frameId, false);
}
/** @type {import('api').ApiHandler<'getStylesheetContent'>} */
@@ -1096,7 +1095,7 @@ export class Backend {
await this._sendMessageTabPromise(
id,
- {action: 'SearchDisplayController.setMode', params: {mode: 'popup'}},
+ {action: 'searchDisplayControllerSetMode', params: {mode: 'popup'}},
{frameId: 0}
);
@@ -1116,7 +1115,7 @@ export class Backend {
try {
const mode = await this._sendMessageTabPromise(
id,
- {action: 'SearchDisplayController.getMode', params: {}},
+ {action: 'searchDisplayControllerGetMode'},
{frameId: 0}
);
return mode === 'popup';
@@ -1196,7 +1195,7 @@ export class Backend {
async _updateSearchQuery(tabId, text, animate) {
await this._sendMessageTabPromise(
tabId,
- {action: 'SearchDisplayController.updateSearchQuery', params: {text, animate}},
+ {action: 'searchDisplayControllerUpdateSearchQuery', params: {text, animate}},
{frameId: 0}
);
}
@@ -1227,7 +1226,7 @@ export class Backend {
this._accessibilityController.update(this._getOptionsFull(false));
- this._sendMessageAllTabsIgnoreResponse('Yomitan.optionsUpdated', {source});
+ this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}});
}
/**
@@ -1635,7 +1634,7 @@ export class Backend {
try {
const response = await this._sendMessageTabPromise(
tabId,
- {action: 'Yomitan.getUrl', params: {}},
+ {action: 'applicationGetUrl'},
{frameId: 0}
);
const url = typeof response === 'object' && response !== null ? /** @type {import('core').SerializableObject} */ (response).url : void 0;
@@ -1806,14 +1805,14 @@ export class Backend {
return new Promise((resolve, reject) => {
/** @type {?import('core').Timeout} */
let timer = null;
- /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */
+ /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */
let onMessage = (message, sender) => {
if (
!sender.tab ||
sender.tab.id !== tabId ||
sender.frameId !== frameId ||
!(typeof message === 'object' && message !== null) ||
- /** @type {import('core').SerializableObject} */ (message).action !== 'yomitanReady'
+ message.action !== 'applicationReady'
) {
return;
}
@@ -1834,7 +1833,7 @@ export class Backend {
chrome.runtime.onMessage.addListener(onMessage);
- this._sendMessageTabPromise(tabId, {action: 'Yomitan.isReady'}, {frameId})
+ this._sendMessageTabPromise(tabId, {action: 'applicationIsReady'}, {frameId})
.then(
(value) => {
if (!value) { return; }
@@ -1893,7 +1892,8 @@ export class Backend {
}
/**
- * @param {{action: string, params: import('core').SerializableObject}} message
+ * @template {import('application').ApiNames} TName
+ * @param {import('application').ApiMessage} message
*/
_sendMessageIgnoreResponse(message) {
const callback = () => this._checkLastError(chrome.runtime.lastError);
@@ -1902,7 +1902,7 @@ export class Backend {
/**
* @param {number} tabId
- * @param {{action: string, params?: import('core').SerializableObject, frameId?: number}} message
+ * @param {import('application').ApiMessageAny} message
* @param {chrome.tabs.MessageSendOptions} options
*/
_sendMessageTabIgnoreResponse(tabId, message, options) {
@@ -1911,25 +1911,25 @@ export class Backend {
}
/**
- * @param {string} action
- * @param {import('core').SerializableObject} params
+ * @param {import('application').ApiMessageAny} message
*/
- _sendMessageAllTabsIgnoreResponse(action, params) {
+ _sendMessageAllTabsIgnoreResponse(message) {
const callback = () => this._checkLastError(chrome.runtime.lastError);
chrome.tabs.query({}, (tabs) => {
for (const tab of tabs) {
const {id} = tab;
if (typeof id !== 'number') { continue; }
- chrome.tabs.sendMessage(id, {action, params}, callback);
+ chrome.tabs.sendMessage(id, message, callback);
}
});
}
/**
+ * @template {import('application').ApiNames} TName
* @param {number} tabId
- * @param {{action: string, params?: import('core').SerializableObject}} message
+ * @param {import('application').ApiMessage} message
* @param {chrome.tabs.MessageSendOptions} options
- * @returns {Promise}
+ * @returns {Promise>}
*/
_sendMessageTabPromise(tabId, message, options) {
return new Promise((resolve, reject) => {
@@ -1938,7 +1938,7 @@ export class Backend {
*/
const callback = (response) => {
try {
- resolve(this._getMessageResponseResult(response));
+ resolve(/** @type {import('application').ApiReturn} */ (this._getMessageResponseResult(response)));
} catch (error) {
reject(error);
}
@@ -1961,11 +1961,11 @@ export class Backend {
if (typeof response !== 'object' || response === null) {
throw new Error('Tab did not respond');
}
- const responseError = /** @type {import('core').SerializedError|undefined} */ (/** @type {import('core').SerializableObject} */ (response).error);
+ const responseError = /** @type {import('core').Response} */ (response).error;
if (typeof responseError === 'object' && responseError !== null) {
throw ExtensionError.deserialize(responseError);
}
- return /** @type {import('core').SerializableObject} */ (response).result;
+ return /** @type {import('core').Response} */ (response).result;
}
/**
@@ -2000,7 +2000,7 @@ export class Backend {
let token = null;
try {
if (typeof tabId === 'number' && typeof frameId === 'number') {
- const action = 'Frontend.setAllVisibleOverride';
+ const action = 'frontendSetAllVisibleOverride';
const params = {value: false, priority: 0, awaitFrame: true};
token = await this._sendMessageTabPromise(tabId, {action, params}, {frameId});
}
@@ -2017,7 +2017,7 @@ export class Backend {
});
} finally {
if (token !== null) {
- const action = 'Frontend.clearAllVisibleOverride';
+ const action = 'frontendClearAllVisibleOverride';
const params = {token};
try {
await this._sendMessageTabPromise(tabId, {action, params}, {frameId});
@@ -2382,7 +2382,7 @@ export class Backend {
*/
_triggerDatabaseUpdated(type, cause) {
this._translator.clearDatabaseCaches();
- this._sendMessageAllTabsIgnoreResponse('Yomitan.databaseUpdated', {type, cause});
+ this._sendMessageAllTabsIgnoreResponse({action: 'applicationDatabaseUpdated', params: {type, cause}});
}
/**
diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js
index 98f67bb0b8..1142121fad 100644
--- a/ext/js/background/script-manager.js
+++ b/ext/js/background/script-manager.js
@@ -16,419 +16,139 @@
* along with this program. If not, see .
*/
-import {isObject} from '../core.js';
-
/**
- * This class is used to manage script injection into content tabs.
+ * Injects a stylesheet into a tab.
+ * @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'.
+ * @param {string} content The content to inject.
+ * - If type is `'file'`, this argument should be a path to a file.
+ * - If type is `'code'`, this argument should be the CSS content.
+ * @param {number} tabId The id of the tab to inject into.
+ * @param {number|undefined} frameId The id of the frame to inject into.
+ * @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames.
+ * @returns {Promise}
*/
-export class ScriptManager {
- /**
- * Creates a new instance of the class.
- */
- constructor() {
- /** @type {Map} */
- this._contentScriptRegistrations = new Map();
- }
-
- /**
- * Injects a stylesheet into a tab.
- * @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'.
- * @param {string} content The content to inject.
- * If type is 'file', this argument should be a path to a file.
- * If type is 'code', this argument should be the CSS content.
- * @param {number} tabId The id of the tab to inject into.
- * @param {number|undefined} frameId The id of the frame to inject into.
- * @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames.
- * @returns {Promise}
- */
- injectStylesheet(type, content, tabId, frameId, allFrames) {
- if (isObject(chrome.scripting) && typeof chrome.scripting.insertCSS === 'function') {
- return this._injectStylesheetMV3(type, content, tabId, frameId, allFrames);
- } else {
- return Promise.reject(new Error('Stylesheet injection not supported'));
- }
- }
-
- /**
- * Injects a script into a tab.
- * @param {string} file The path to a file to inject.
- * @param {number} tabId The id of the tab to inject into.
- * @param {number|undefined} frameId The id of the frame to inject into.
- * @param {boolean} allFrames Whether or not the script should be injected into all frames.
- * @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection.
- */
- injectScript(file, tabId, frameId, allFrames) {
- if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') {
- return this._injectScriptMV3(file, tabId, frameId, allFrames);
- } else {
- return Promise.reject(new Error('Script injection not supported'));
- }
- }
-
- /**
- * Checks whether or not a content script is registered.
- * @param {string} id The identifier used with a call to `registerContentScript`.
- * @returns {Promise} `true` if a script is registered, `false` otherwise.
- */
- async isContentScriptRegistered(id) {
- if (this._contentScriptRegistrations.has(id)) {
- return true;
- }
- if (isObject(chrome.scripting) && typeof chrome.scripting.getRegisteredContentScripts === 'function') {
- const scripts = await new Promise((resolve, reject) => {
- chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve(result);
- }
- });
- });
- for (const script of scripts) {
- if (script.id === id) {
- return true;
- }
+export function injectStylesheet(type, content, tabId, frameId, allFrames) {
+ return new Promise((resolve, reject) => {
+ /** @type {chrome.scripting.InjectionTarget} */
+ const target = {
+ tabId,
+ allFrames
+ };
+ /** @type {chrome.scripting.CSSInjection} */
+ const details = (
+ type === 'file' ?
+ {origin: 'AUTHOR', files: [content], target} :
+ {origin: 'USER', css: content, target}
+ );
+ if (!allFrames && typeof frameId === 'number') {
+ details.target.frameIds = [frameId];
+ }
+ chrome.scripting.insertCSS(details, () => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve();
}
- }
- return false;
- }
-
- /**
- * Registers a dynamic content script.
- * Note: if the fallback handler is used and the 'webNavigation' permission isn't granted,
- * there is a possibility that the script can be injected more than once due to the events used.
- * Therefore, a reentrant check may need to be performed by the content script.
- * @param {string} id A unique identifier for the registration.
- * @param {import('script-manager').RegistrationDetails} details The script registration details.
- * @throws An error is thrown if the id is already in use.
- */
- async registerContentScript(id, details) {
- if (await this.isContentScriptRegistered(id)) {
- throw new Error('Registration already exists');
- }
-
- if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') {
- const details2 = this._createContentScriptRegistrationOptionsChrome(details, id);
- await /** @type {Promise} */ (new Promise((resolve, reject) => {
- chrome.scripting.registerContentScripts([details2], () => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve();
- }
- });
- }));
- this._contentScriptRegistrations.set(id, null);
- return;
- }
-
- // Fallback
- this._registerContentScriptFallback(id, details);
- }
+ });
+ });
+}
- /**
- * Unregisters a previously registered content script.
- * @param {string} id The identifier passed to a previous call to `registerContentScript`.
- * @returns {Promise} `true` if the content script was unregistered, `false` otherwise.
- */
- async unregisterContentScript(id) {
- if (isObject(chrome.scripting) && typeof chrome.scripting.unregisterContentScripts === 'function') {
- this._contentScriptRegistrations.delete(id);
- try {
- await this._unregisterContentScriptMV3(id);
- return true;
- } catch (e) {
- return false;
+/**
+ * Checks whether or not a content script is registered.
+ * @param {string} id The identifier used with a call to `registerContentScript`.
+ * @returns {Promise} `true` if a script is registered, `false` otherwise.
+ */
+export async function isContentScriptRegistered(id) {
+ const scripts = await new Promise((resolve, reject) => {
+ chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve(result);
}
+ });
+ });
+ for (const script of scripts) {
+ if (script.id === id) {
+ return true;
}
-
- // Fallback
- const registration = this._contentScriptRegistrations.get(id);
- if (typeof registration === 'undefined') { return false; }
- this._contentScriptRegistrations.delete(id);
- if (registration !== null && typeof registration.unregister === 'function') {
- await registration.unregister();
- }
- return true;
}
+ return false;
+}
- /**
- * Gets the optional permissions required to register a content script.
- * @returns {string[]} An array of the required permissions, which may be empty.
- */
- getRequiredContentScriptRegistrationPermissions() {
- if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') {
- return [];
- }
-
- // Fallback
- return ['webNavigation'];
+/**
+ * Registers a dynamic content script.
+ * Note: if the fallback handler is used and the 'webNavigation' permission isn't granted,
+ * there is a possibility that the script can be injected more than once due to the events used.
+ * Therefore, a reentrant check may need to be performed by the content script.
+ * @param {string} id A unique identifier for the registration.
+ * @param {import('script-manager').RegistrationDetails} details The script registration details.
+ * @throws An error is thrown if the id is already in use.
+ */
+export async function registerContentScript(id, details) {
+ if (await isContentScriptRegistered(id)) {
+ throw new Error('Registration already exists');
}
- // Private
-
- /**
- * @param {'file'|'code'} type
- * @param {string} content
- * @param {number} tabId
- * @param {number|undefined} frameId
- * @param {boolean} allFrames
- * @returns {Promise}
- */
- _injectStylesheetMV3(type, content, tabId, frameId, allFrames) {
- return new Promise((resolve, reject) => {
- /** @type {chrome.scripting.InjectionTarget} */
- const target = {
- tabId,
- allFrames
- };
- /** @type {chrome.scripting.CSSInjection} */
- const details = (
- type === 'file' ?
- {origin: 'AUTHOR', files: [content], target} :
- {origin: 'USER', css: content, target}
- );
- if (!allFrames && typeof frameId === 'number') {
- details.target.frameIds = [frameId];
+ const details2 = createContentScriptRegistrationOptions(details, id);
+ await /** @type {Promise} */ (new Promise((resolve, reject) => {
+ chrome.scripting.registerContentScripts([details2], () => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve();
}
- chrome.scripting.insertCSS(details, () => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve();
- }
- });
});
- }
+ }));
+}
- /**
- * @param {string} file
- * @param {number} tabId
- * @param {number|undefined} frameId
- * @param {boolean} allFrames
- * @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection.
- */
- _injectScriptMV3(file, tabId, frameId, allFrames) {
- return new Promise((resolve, reject) => {
- /** @type {chrome.scripting.ScriptInjection} */
- const details = {
- injectImmediately: true,
- files: [file],
- target: {tabId, allFrames}
- };
- if (!allFrames && typeof frameId === 'number') {
- details.target.frameIds = [frameId];
+/**
+ * Unregisters a previously registered content script.
+ * @param {string} id The identifier passed to a previous call to `registerContentScript`.
+ * @returns {Promise}
+ */
+export async function unregisterContentScript(id) {
+ return new Promise((resolve, reject) => {
+ chrome.scripting.unregisterContentScripts({ids: [id]}, () => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve();
}
- chrome.scripting.executeScript(details, (results) => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- const {frameId: frameId2, result} = results[0];
- resolve({frameId: frameId2, result});
- }
- });
});
- }
-
- /**
- * @param {string} id
- * @returns {Promise}
- */
- _unregisterContentScriptMV3(id) {
- return new Promise((resolve, reject) => {
- chrome.scripting.unregisterContentScripts({ids: [id]}, () => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve();
- }
- });
- });
- }
-
- /**
- * @param {import('script-manager').RegistrationDetails} details
- * @returns {browser.contentScripts.RegisteredContentScriptOptions}
- */
- _createContentScriptRegistrationOptionsFirefox(details) {
- const {css, js, matchAboutBlank} = details;
- /** @type {browser.contentScripts.RegisteredContentScriptOptions} */
- const options = {};
- if (typeof matchAboutBlank !== 'undefined') {
- options.matchAboutBlank = matchAboutBlank;
- }
- if (Array.isArray(css)) {
- options.css = css.map((file) => ({file}));
- }
- if (Array.isArray(js)) {
- options.js = js.map((file) => ({file}));
- }
- this._initializeContentScriptRegistrationOptionsGeneric(details, options);
- return options;
- }
-
- /**
- * @param {import('script-manager').RegistrationDetails} details
- * @param {string} id
- * @returns {chrome.scripting.RegisteredContentScript}
- */
- _createContentScriptRegistrationOptionsChrome(details, id) {
- const {css, js} = details;
- /** @type {chrome.scripting.RegisteredContentScript} */
- const options = {
- id: id,
- persistAcrossSessions: true
- };
- if (Array.isArray(css)) {
- options.css = [...css];
- }
- if (Array.isArray(js)) {
- options.js = [...js];
- }
- this._initializeContentScriptRegistrationOptionsGeneric(details, options);
- return options;
- }
+ });
+}
- /**
- * @param {import('script-manager').RegistrationDetails} details
- * @param {chrome.scripting.RegisteredContentScript|browser.contentScripts.RegisteredContentScriptOptions} options
- */
- _initializeContentScriptRegistrationOptionsGeneric(details, options) {
- const {allFrames, excludeMatches, matches, runAt} = details;
- if (typeof allFrames !== 'undefined') {
- options.allFrames = allFrames;
- }
- if (Array.isArray(excludeMatches)) {
- options.excludeMatches = [...excludeMatches];
- }
- if (Array.isArray(matches)) {
- options.matches = [...matches];
- }
- if (typeof runAt !== 'undefined') {
- options.runAt = runAt;
- }
+/**
+ * @param {import('script-manager').RegistrationDetails} details
+ * @param {string} id
+ * @returns {chrome.scripting.RegisteredContentScript}
+ */
+function createContentScriptRegistrationOptions(details, id) {
+ const {css, js, allFrames, matches, runAt} = details;
+ /** @type {chrome.scripting.RegisteredContentScript} */
+ const options = {
+ id: id,
+ persistAcrossSessions: true
+ };
+ if (Array.isArray(css)) {
+ options.css = [...css];
}
-
- /**
- * @param {string[]} array
- * @param {boolean} firefoxConvention
- * @returns {string[]|browser.extensionTypes.ExtensionFileOrCode[]}
- */
- _convertFileArray(array, firefoxConvention) {
- return firefoxConvention ? array.map((file) => ({file})) : [...array];
+ if (Array.isArray(js)) {
+ options.js = [...js];
}
-
- /**
- * @param {string} id
- * @param {import('script-manager').RegistrationDetails} details
- */
- _registerContentScriptFallback(id, details) {
- const {allFrames, css, js, matchAboutBlank, runAt, urlMatches} = details;
- /** @type {import('script-manager').ContentScriptInjectionDetails} */
- const details2 = {allFrames, css, js, matchAboutBlank, runAt, urlRegex: /** @type {?RegExp} */ (null)};
- /** @type {() => Promise} */
- let unregister;
- const webNavigationEvent = this._getWebNavigationEvent(runAt);
- if (typeof webNavigationEvent === 'object' && webNavigationEvent !== null) {
- /**
- * @param {chrome.webNavigation.WebNavigationFramedCallbackDetails} details
- */
- const onTabCommitted = ({url, tabId, frameId}) => {
- this._injectContentScript(true, details2, null, url, tabId, frameId);
- };
- const filter = {url: [{urlMatches}]};
- webNavigationEvent.addListener(onTabCommitted, filter);
- unregister = async () => webNavigationEvent.removeListener(onTabCommitted);
- } else {
- /**
- * @param {number} tabId
- * @param {chrome.tabs.TabChangeInfo} changeInfo
- * @param {chrome.tabs.Tab} tab
- */
- const onTabUpdated = (tabId, {status}, {url}) => {
- if (typeof status === 'string' && typeof url === 'string') {
- this._injectContentScript(false, details2, status, url, tabId, void 0);
- }
- };
- try {
- // Firefox
- /** @type {browser.tabs.UpdateFilter} */
- const extraParameters = {urls: [urlMatches], properties: ['status']};
- browser.tabs.onUpdated.addListener(
- /** @type {(tabId: number, changeInfo: browser.tabs._OnUpdatedChangeInfo, tab: browser.tabs.Tab) => void} */ (onTabUpdated),
- extraParameters
- );
- } catch (e) {
- // Chrome
- details2.urlRegex = new RegExp(urlMatches);
- chrome.tabs.onUpdated.addListener(onTabUpdated);
- }
- unregister = async () => chrome.tabs.onUpdated.removeListener(onTabUpdated);
- }
- this._contentScriptRegistrations.set(id, {unregister});
+ if (typeof allFrames !== 'undefined') {
+ options.allFrames = allFrames;
}
-
- /**
- * @param {import('script-manager').RunAt} runAt
- * @returns {?(chrome.webNavigation.WebNavigationFramedEvent|chrome.webNavigation.WebNavigationTransitionalEvent)}
- */
- _getWebNavigationEvent(runAt) {
- const {webNavigation} = chrome;
- if (!isObject(webNavigation)) { return null; }
- switch (runAt) {
- case 'document_start':
- return webNavigation.onCommitted;
- case 'document_end':
- return webNavigation.onDOMContentLoaded;
- default: // 'document_idle':
- return webNavigation.onCompleted;
- }
+ if (Array.isArray(matches)) {
+ options.matches = [...matches];
}
-
- /**
- * @param {boolean} isWebNavigation
- * @param {import('script-manager').ContentScriptInjectionDetails} details
- * @param {?string} status
- * @param {string} url
- * @param {number} tabId
- * @param {number|undefined} frameId
- */
- async _injectContentScript(isWebNavigation, details, status, url, tabId, frameId) {
- const {urlRegex} = details;
- if (urlRegex !== null && !urlRegex.test(url)) { return; }
-
- let {allFrames, css, js, runAt} = details;
-
- if (isWebNavigation) {
- if (allFrames) {
- allFrames = false;
- } else {
- if (frameId !== 0) { return; }
- }
- } else {
- if (runAt === 'document_start') {
- if (status !== 'loading') { return; }
- } else { // 'document_end', 'document_idle'
- if (status !== 'complete') { return; }
- }
- }
-
- const promises = [];
- if (Array.isArray(css)) {
- for (const file of css) {
- promises.push(this.injectStylesheet('file', file, tabId, frameId, allFrames));
- }
- }
- if (Array.isArray(js)) {
- for (const file of js) {
- promises.push(this.injectScript(file, tabId, frameId, allFrames));
- }
- }
- await Promise.all(promises);
+ if (typeof runAt !== 'undefined') {
+ options.runAt = runAt;
}
+ return options;
}
diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js
index c23515383e..423115f18c 100644
--- a/ext/js/comm/api.js
+++ b/ext/js/comm/api.js
@@ -156,21 +156,19 @@ export class API {
/**
* @param {import('api').ApiParam<'sendMessageToFrame', 'frameId'>} frameId
- * @param {import('api').ApiParam<'sendMessageToFrame', 'action'>} action
- * @param {import('api').ApiParam<'sendMessageToFrame', 'params'>} [params]
+ * @param {import('api').ApiParam<'sendMessageToFrame', 'message'>} message
* @returns {Promise>}
*/
- sendMessageToFrame(frameId, action, params) {
- return this._invoke('sendMessageToFrame', {frameId, action, params});
+ sendMessageToFrame(frameId, message) {
+ return this._invoke('sendMessageToFrame', {frameId, message});
}
/**
- * @param {import('api').ApiParam<'broadcastTab', 'action'>} action
- * @param {import('api').ApiParam<'broadcastTab', 'params'>} params
+ * @param {import('api').ApiParam<'broadcastTab', 'message'>} message
* @returns {Promise>}
*/
- broadcastTab(action, params) {
- return this._invoke('broadcastTab', {action, params});
+ broadcastTab(message) {
+ return this._invoke('broadcastTab', {message});
}
/**
diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js
index 5e997622fc..cb591ca995 100644
--- a/ext/js/comm/frame-client.js
+++ b/ext/js/comm/frame-client.js
@@ -110,14 +110,14 @@ export class FrameClient {
contentWindow.postMessage({action, params}, targetOrigin);
};
- /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
+ /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
const onMessage = (message) => {
onMessageInner(message);
return false;
};
/**
- * @param {import('extension').ChromeRuntimeMessageWithFrameId} message
+ * @param {import('application').ApiMessageAny} message
*/
const onMessageInner = async (message) => {
try {
@@ -130,7 +130,7 @@ export class FrameClient {
switch (action) {
case 'frameEndpointReady':
{
- const {secret} = /** @type {import('frame-client').FrameEndpointReadyDetails} */ (params);
+ const {secret} = params;
const token = generateId(16);
tokenMap.set(secret, token);
postMessage('frameEndpointConnect', {secret, token, hostFrameId});
@@ -138,7 +138,7 @@ export class FrameClient {
break;
case 'frameEndpointConnected':
{
- const {secret, token} = /** @type {import('frame-client').FrameEndpointConnectedDetails} */ (params);
+ const {secret, token} = params;
const frameId = message.frameId;
const token2 = tokenMap.get(secret);
if (typeof token2 !== 'undefined' && token === token2 && typeof frameId === 'number') {
diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js
index c338e143c4..4c5f58c106 100644
--- a/ext/js/comm/frame-endpoint.js
+++ b/ext/js/comm/frame-endpoint.js
@@ -41,7 +41,7 @@ export class FrameEndpoint {
}
/** @type {import('frame-client').FrameEndpointReadyDetails} */
const details = {secret: this._secret};
- yomitan.api.broadcastTab('frameEndpointReady', details);
+ yomitan.api.broadcastTab({action: 'frameEndpointReady', params: details});
}
/**
@@ -83,6 +83,6 @@ export class FrameEndpoint {
this._eventListeners.removeAllEventListeners();
/** @type {import('frame-client').FrameEndpointConnectedDetails} */
const details = {secret, token};
- yomitan.api.sendMessageToFrame(hostFrameId, 'frameEndpointConnected', details);
+ yomitan.api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details});
}
}
diff --git a/ext/js/data/sandbox/anki-note-data-creator.js b/ext/js/data/sandbox/anki-note-data-creator.js
index 9d93b49789..c0a1186980 100644
--- a/ext/js/data/sandbox/anki-note-data-creator.js
+++ b/ext/js/data/sandbox/anki-note-data-creator.js
@@ -55,6 +55,8 @@ export class AnkiNoteDataCreator {
const context2 = this.createCachedValue(this._getPublicContext.bind(this, context));
const pitches = this.createCachedValue(this._getPitches.bind(this, dictionaryEntry));
const pitchCount = this.createCachedValue(this._getPitchCount.bind(this, pitches));
+ const phoneticTranscriptions = this.createCachedValue(this._getPhoneticTranscriptions.bind(this, dictionaryEntry));
+
if (typeof media !== 'object' || media === null || Array.isArray(media)) {
media = {
audio: void 0,
@@ -82,6 +84,7 @@ export class AnkiNoteDataCreator {
get uniqueReadings() { return self.getCachedValue(uniqueReadings); },
get pitches() { return self.getCachedValue(pitches); },
get pitchCount() { return self.getCachedValue(pitchCount); },
+ get phoneticTranscriptions() { return self.getCachedValue(phoneticTranscriptions); },
get context() { return self.getCachedValue(context2); },
media,
dictionaryEntry
@@ -193,7 +196,11 @@ export class AnkiNoteDataCreator {
for (const {dictionary, pronunciations} of DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry)) {
/** @type {import('anki-templates').Pitch[]} */
const pitches = [];
- for (const {terms, reading, position, nasalPositions, devoicePositions, tags, exclusiveTerms, exclusiveReadings} of pronunciations) {
+ for (const groupedPronunciation of pronunciations) {
+ const {pronunciation} = groupedPronunciation;
+ if (pronunciation.type !== 'pitch-accent') { continue; }
+ const {position, nasalPositions, devoicePositions, tags} = pronunciation;
+ const {terms, reading, exclusiveTerms, exclusiveReadings} = groupedPronunciation;
pitches.push({
expressions: terms,
reading,
@@ -211,6 +218,35 @@ export class AnkiNoteDataCreator {
return results;
}
+ /**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TranscriptionGroup[]}
+ */
+ _getPhoneticTranscriptions(dictionaryEntry) {
+ const results = [];
+ if (dictionaryEntry.type === 'term') {
+ for (const {dictionary, pronunciations} of DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry)) {
+ const phoneticTranscriptions = [];
+ for (const groupedPronunciation of pronunciations) {
+ const {pronunciation} = groupedPronunciation;
+ if (pronunciation.type !== 'phonetic-transcription') { continue; }
+ const {ipa, tags} = pronunciation;
+ const {terms, reading, exclusiveTerms, exclusiveReadings} = groupedPronunciation;
+ phoneticTranscriptions.push({
+ expressions: terms,
+ reading,
+ ipa,
+ tags,
+ exclusiveExpressions: exclusiveTerms,
+ exclusiveReadings
+ });
+ }
+ results.push({dictionary, phoneticTranscriptions});
+ }
+ }
+ return results;
+ }
+
/**
* @param {import('anki-templates-internal').CachedValue} cachedPitches
* @returns {number}
@@ -353,6 +389,7 @@ export class AnkiNoteDataCreator {
const expressions = this.createCachedValue(this._getTermExpressions.bind(this, dictionaryEntry));
const frequencies = this.createCachedValue(this._getTermFrequencies.bind(this, dictionaryEntry));
const pitches = this.createCachedValue(this._getTermPitches.bind(this, dictionaryEntry));
+ const phoneticTranscriptions = this.createCachedValue(this._getTermPhoneticTranscriptions.bind(this, dictionaryEntry));
const glossary = this.createCachedValue(this._getTermGlossaryArray.bind(this, dictionaryEntry, type));
const cloze = this.createCachedValue(this._getCloze.bind(this, dictionaryEntry, context));
const furiganaSegments = this.createCachedValue(this._getTermFuriganaSegments.bind(this, dictionaryEntry, type));
@@ -389,6 +426,7 @@ export class AnkiNoteDataCreator {
get definitions() { return self.getCachedValue(commonInfo).definitions; },
get frequencies() { return self.getCachedValue(frequencies); },
get pitches() { return self.getCachedValue(pitches); },
+ get phoneticTranscriptions() { return self.getCachedValue(phoneticTranscriptions); },
sourceTermExactMatchCount,
url,
get cloze() { return self.getCachedValue(cloze); },
@@ -485,15 +523,16 @@ export class AnkiNoteDataCreator {
/**
* @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {import('anki-templates').TermPronunciation[]}
+ * @returns {import('anki-templates').TermPitchAccent[]}
*/
_getTermPitches(dictionaryEntry) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const results = [];
const {headwords} = dictionaryEntry;
- for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches} of dictionaryEntry.pronunciations) {
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) {
const {term, reading} = headwords[headwordIndex];
+ const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent');
const cachedPitches = this.createCachedValue(this._getTermPitchesInner.bind(this, pitches));
results.push({
index: results.length,
@@ -512,8 +551,8 @@ export class AnkiNoteDataCreator {
}
/**
- * @param {import('dictionary').TermPitch[]} pitches
- * @returns {import('anki-templates').TermPitch[]}
+ * @param {import('dictionary').PitchAccent[]} pitches
+ * @returns {import('anki-templates').PitchAccent[]}
*/
_getTermPitchesInner(pitches) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -529,6 +568,52 @@ export class AnkiNoteDataCreator {
return results;
}
+ /**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermPhoneticTranscription[]}
+ */
+ _getTermPhoneticTranscriptions(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ const phoneticTranscriptions = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'phonetic-transcription');
+ const termPhoneticTranscriptions = this._getTermPhoneticTranscriptionsInner(phoneticTranscriptions);
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get phoneticTranscriptions() { return termPhoneticTranscriptions; }
+ });
+ }
+
+ return results;
+ }
+
+ /**
+ * @param {import('dictionary').PhoneticTranscription[]} phoneticTranscriptions
+ * @returns {import('anki-templates').PhoneticTranscription[]}
+ */
+ _getTermPhoneticTranscriptionsInner(phoneticTranscriptions) {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const self = this;
+ const results = [];
+ for (const {ipa, tags} of phoneticTranscriptions) {
+ const cachedTags = this.createCachedValue(this._convertTags.bind(this, tags));
+ results.push({
+ ipa,
+ get tags() { return self.getCachedValue(cachedTags); }
+ });
+ }
+ return results;
+ }
+
/**
* @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
* @returns {import('anki-templates').TermHeadword[]}
@@ -592,16 +677,17 @@ export class AnkiNoteDataCreator {
/**
* @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
* @param {number} i
- * @returns {import('anki-templates').TermPronunciation[]}
+ * @returns {import('anki-templates').TermPitchAccent[]}
*/
_getTermExpressionPitches(dictionaryEntry, i) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const results = [];
- const {headwords, pronunciations} = dictionaryEntry;
- for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches} of pronunciations) {
+ const {headwords, pronunciations: termPronunciations} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of termPronunciations) {
if (headwordIndex !== i) { continue; }
const {term, reading} = headwords[headwordIndex];
+ const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent');
const cachedPitches = this.createCachedValue(this._getTermPitchesInner.bind(this, pitches));
results.push({
index: results.length,
diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js
index a54b043bbf..50ae4b1194 100644
--- a/ext/js/dictionary/dictionary-data-util.js
+++ b/ext/js/dictionary/dictionary-data-util.js
@@ -135,7 +135,7 @@ export class DictionaryDataUtil {
* @returns {import('dictionary-data-util').DictionaryGroupedPronunciations[]}
*/
static getGroupedPronunciations(dictionaryEntry) {
- const {headwords, pronunciations} = dictionaryEntry;
+ const {headwords, pronunciations: termPronunciations} = dictionaryEntry;
const allTerms = new Set();
const allReadings = new Set();
@@ -146,23 +146,20 @@ export class DictionaryDataUtil {
/** @type {Map} */
const groupedPronunciationsMap = new Map();
- for (const {headwordIndex, dictionary, pitches} of pronunciations) {
+ for (const {headwordIndex, dictionary, pronunciations} of termPronunciations) {
const {term, reading} = headwords[headwordIndex];
let dictionaryGroupedPronunciationList = groupedPronunciationsMap.get(dictionary);
if (typeof dictionaryGroupedPronunciationList === 'undefined') {
dictionaryGroupedPronunciationList = [];
groupedPronunciationsMap.set(dictionary, dictionaryGroupedPronunciationList);
}
- for (const {position, nasalPositions, devoicePositions, tags} of pitches) {
- let groupedPronunciation = this._findExistingGroupedPronunciation(reading, position, nasalPositions, devoicePositions, tags, dictionaryGroupedPronunciationList);
+ for (const pronunciation of pronunciations) {
+ let groupedPronunciation = this._findExistingGroupedPronunciation(reading, pronunciation, dictionaryGroupedPronunciationList);
if (groupedPronunciation === null) {
groupedPronunciation = {
+ pronunciation,
terms: new Set(),
- reading,
- position,
- nasalPositions,
- devoicePositions,
- tags
+ reading
};
dictionaryGroupedPronunciationList.push(groupedPronunciation);
}
@@ -177,28 +174,43 @@ export class DictionaryDataUtil {
/** @type {import('dictionary-data-util').GroupedPronunciation[]} */
const pronunciations2 = [];
for (const groupedPronunciation of dictionaryGroupedPronunciationList) {
- const {terms, reading, position, nasalPositions, devoicePositions, tags} = groupedPronunciation;
+ const {pronunciation, terms, reading} = groupedPronunciation;
const exclusiveTerms = !this._areSetsEqual(terms, allTerms) ? this._getSetIntersection(terms, allTerms) : [];
const exclusiveReadings = [];
if (multipleReadings) {
exclusiveReadings.push(reading);
}
pronunciations2.push({
+ pronunciation,
terms: [...terms],
reading,
- position,
- nasalPositions,
- devoicePositions,
- tags,
exclusiveTerms,
exclusiveReadings
});
}
+
results2.push({dictionary, pronunciations: pronunciations2});
}
return results2;
}
+ /**
+ * @template {import('dictionary').PronunciationType} T
+ * @param {import('dictionary').Pronunciation[]} pronunciations
+ * @param {T} type
+ * @returns {import('dictionary').PronunciationGeneric[]}
+ */
+ static getPronunciationsOfType(pronunciations, type) {
+ /** @type {import('dictionary').PronunciationGeneric[]} */
+ const results = [];
+ for (const pronunciation of pronunciations) {
+ if (pronunciation.type !== type) { continue; }
+ // This is type safe, but for some reason the cast is needed.
+ results.push(/** @type {import('dictionary').PronunciationGeneric} */ (pronunciation));
+ }
+ return results;
+ }
+
/**
* @param {import('dictionary').Tag[]|import('anki-templates').Tag[]} termTags
* @returns {import('dictionary-data-util').TermFrequencyType}
@@ -288,26 +300,49 @@ export class DictionaryDataUtil {
/**
* @param {string} reading
- * @param {number} position
- * @param {number[]} nasalPositions
- * @param {number[]} devoicePositions
- * @param {import('dictionary').Tag[]} tags
+ * @param {import('dictionary').Pronunciation} pronunciation
* @param {import('dictionary-data-util').GroupedPronunciationInternal[]} groupedPronunciationList
* @returns {?import('dictionary-data-util').GroupedPronunciationInternal}
*/
- static _findExistingGroupedPronunciation(reading, position, nasalPositions, devoicePositions, tags, groupedPronunciationList) {
- for (const pitchInfo of groupedPronunciationList) {
- if (
- pitchInfo.reading === reading &&
- pitchInfo.position === position &&
- this._areArraysEqual(pitchInfo.nasalPositions, nasalPositions) &&
- this._areArraysEqual(pitchInfo.devoicePositions, devoicePositions) &&
- this._areTagListsEqual(pitchInfo.tags, tags)
- ) {
- return pitchInfo;
+ static _findExistingGroupedPronunciation(reading, pronunciation, groupedPronunciationList) {
+ const existingGroupedPronunciation = groupedPronunciationList.find((groupedPronunciation) => {
+ return groupedPronunciation.reading === reading && this._arePronunciationsEquivalent(groupedPronunciation, pronunciation);
+ });
+
+ return existingGroupedPronunciation || null;
+ }
+
+ /**
+ * @param {import('dictionary-data-util').GroupedPronunciationInternal} groupedPronunciation
+ * @param {import('dictionary').Pronunciation} pronunciation2
+ * @returns {boolean}
+ */
+ static _arePronunciationsEquivalent({pronunciation: pronunciation1}, pronunciation2) {
+ if (
+ pronunciation1.type !== pronunciation2.type ||
+ !this._areTagListsEqual(pronunciation1.tags, pronunciation2.tags)
+ ) {
+ return false;
+ }
+ switch (pronunciation1.type) {
+ case 'pitch-accent':
+ {
+ // This cast is valid based on the type check at the start of the function.
+ const pitchAccent2 = /** @type {import('dictionary').PitchAccent} */ (pronunciation2);
+ return (
+ pronunciation1.position === pitchAccent2.position &&
+ this._areArraysEqual(pronunciation1.nasalPositions, pitchAccent2.nasalPositions) &&
+ this._areArraysEqual(pronunciation1.devoicePositions, pitchAccent2.devoicePositions)
+ );
+ }
+ case 'phonetic-transcription':
+ {
+ // This cast is valid based on the type check at the start of the function.
+ const phoneticTranscription2 = /** @type {import('dictionary').PhoneticTranscription} */ (pronunciation2);
+ return pronunciation1.ipa === phoneticTranscription2.ipa;
}
}
- return null;
+ return true;
}
/**
diff --git a/ext/js/dictionary/dictionary-database.js b/ext/js/dictionary/dictionary-database.js
index 45c5c6fdb9..02db6322a0 100644
--- a/ext/js/dictionary/dictionary-database.js
+++ b/ext/js/dictionary/dictionary-database.js
@@ -627,6 +627,8 @@ export class DictionaryDatabase {
return {index, term, mode, data, dictionary};
case 'pitch':
return {index, term, mode, data, dictionary};
+ case 'ipa':
+ return {index, term, mode, data, dictionary};
default:
throw new Error(`Unknown mode: ${mode}`);
}
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js
index b91d0ce917..3a2a562115 100644
--- a/ext/js/display/display-generator.js
+++ b/ext/js/display/display-generator.js
@@ -626,7 +626,7 @@ export class DisplayGenerator {
n1.appendChild(tag);
let hasTags = false;
- for (const {tags} of pronunciations) {
+ for (const {pronunciation: {tags}} of pronunciations) {
if (tags.length > 0) {
hasTags = true;
break;
@@ -645,8 +645,52 @@ export class DisplayGenerator {
* @returns {HTMLElement}
*/
_createPronunciation(details) {
+ const {pronunciation} = details;
+ switch (pronunciation.type) {
+ case 'pitch-accent':
+ return this._createPronunciationPitchAccent(pronunciation, details);
+ case 'phonetic-transcription':
+ return this._createPronunciationPhoneticTranscription(pronunciation, details);
+ }
+ }
+
+
+ /**
+ * @param {import('dictionary').PhoneticTranscription} pronunciation
+ * @param {import('dictionary-data-util').GroupedPronunciation} details
+ * @returns {HTMLElement}
+ */
+ _createPronunciationPhoneticTranscription(pronunciation, details) {
+ const {ipa, tags} = pronunciation;
+ const {exclusiveTerms, exclusiveReadings} = details;
+
+ const node = this._instantiate('pronunciation');
+
+ node.dataset.tagCount = `${tags.length}`;
+
+ let n = this._querySelector(node, '.pronunciation-tag-list');
+ this._appendMultiple(n, this._createTag.bind(this), tags);
+
+ n = this._querySelector(node, '.pronunciation-disambiguation-list');
+ this._createPronunciationDisambiguations(n, exclusiveTerms, exclusiveReadings);
+
+ n = this._querySelector(node, '.pronunciation-text-container');
+
+ this._setTextContent(n, ipa);
+
+ return node;
+ }
+
+ /**
+ * @param {import('dictionary').PitchAccent} pitchAccent
+ * @param {import('dictionary-data-util').GroupedPronunciation} details
+ * @returns {HTMLElement}
+ */
+ _createPronunciationPitchAccent(pitchAccent, details) {
const jp = this._japaneseUtil;
- const {reading, position, nasalPositions, devoicePositions, tags, exclusiveTerms, exclusiveReadings} = details;
+
+ const {position, nasalPositions, devoicePositions, tags} = pitchAccent;
+ const {reading, exclusiveTerms, exclusiveReadings} = details;
const morae = jp.getKanaMorae(reading);
const node = this._instantiate('pronunciation');
@@ -666,6 +710,7 @@ export class DisplayGenerator {
n.appendChild(this._pronunciationGenerator.createPronunciationDownstepPosition(position));
n = this._querySelector(node, '.pronunciation-text-container');
+
n.lang = 'ja';
n.appendChild(this._pronunciationGenerator.createPronunciationText(morae, position, nasalPositions, devoicePositions));
@@ -954,20 +999,21 @@ export class DisplayGenerator {
/**
* @param {string} reading
- * @param {import('dictionary').TermPronunciation[]} pronunciations
+ * @param {import('dictionary').TermPronunciation[]} termPronunciations
* @param {string[]} wordClasses
* @param {number} headwordIndex
* @returns {?string}
*/
- _getPronunciationCategories(reading, pronunciations, wordClasses, headwordIndex) {
- if (pronunciations.length === 0) { return null; }
+ _getPronunciationCategories(reading, termPronunciations, wordClasses, headwordIndex) {
+ if (termPronunciations.length === 0) { return null; }
const isVerbOrAdjective = DictionaryDataUtil.isNonNounVerbOrAdjective(wordClasses);
/** @type {Set} */
const categories = new Set();
- for (const pronunciation of pronunciations) {
- if (pronunciation.headwordIndex !== headwordIndex) { continue; }
- for (const {position} of pronunciation.pitches) {
- const category = this._japaneseUtil.getPitchCategory(reading, position, isVerbOrAdjective);
+ for (const termPronunciation of termPronunciations) {
+ if (termPronunciation.headwordIndex !== headwordIndex) { continue; }
+ for (const pronunciation of termPronunciation.pronunciations) {
+ if (pronunciation.type !== 'pitch-accent') { continue; }
+ const category = this._japaneseUtil.getPitchCategory(reading, pronunciation.position, isVerbOrAdjective);
if (category !== null) {
categories.add(category);
}
diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js
index 482afd56c1..6767d201d2 100644
--- a/ext/js/display/search-display-controller.js
+++ b/ext/js/display/search-display-controller.js
@@ -18,7 +18,8 @@
import * as wanakana from '../../lib/wanakana.js';
import {ClipboardMonitor} from '../comm/clipboard-monitor.js';
-import {EventListenerCollection, invokeMessageHandler} from '../core.js';
+import {EventListenerCollection} from '../core.js';
+import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';
import {querySelectorNotNull} from '../dom/query-selector.js';
import {yomitan} from '../yomitan.js';
@@ -75,8 +76,12 @@ export class SearchDisplayController {
getText: yomitan.api.clipboardGet.bind(yomitan.api)
}
});
- /** @type {import('core').MessageHandlerMap} */
- this._messageHandlers = new Map();
+ /** @type {import('application').ApiMap} */
+ this._apiMap = createApiMap([
+ ['searchDisplayControllerGetMode', this._onMessageGetMode.bind(this)],
+ ['searchDisplayControllerSetMode', this._onMessageSetMode.bind(this)],
+ ['searchDisplayControllerUpdateSearchQuery', this._onExternalSearchUpdate.bind(this)]
+ ]);
}
/** */
@@ -94,13 +99,6 @@ export class SearchDisplayController {
this._display.hotkeyHandler.registerActions([
['focusSearchBox', this._onActionFocusSearchBox.bind(this)]
]);
- /* eslint-disable no-multi-spaces */
- this._registerMessageHandlers([
- ['SearchDisplayController.getMode', this._onMessageGetMode.bind(this)],
- ['SearchDisplayController.setMode', this._onMessageSetMode.bind(this)],
- ['SearchDisplayController.updateSearchQuery', this._onExternalSearchUpdate.bind(this)]
- ]);
- /* eslint-enable no-multi-spaces */
this._updateClipboardMonitorEnabled();
@@ -140,32 +138,21 @@ export class SearchDisplayController {
// Messages
- /**
- * @param {{mode: import('display').SearchMode}} details
- */
+ /** @type {import('application').ApiHandler<'searchDisplayControllerSetMode'>} */
_onMessageSetMode({mode}) {
this.setMode(mode);
}
- /**
- * @returns {import('display').SearchMode}
- */
+ /** @type {import('application').ApiHandler<'searchDisplayControllerGetMode'>} */
_onMessageGetMode() {
return this._searchPersistentStateController.mode;
}
// Private
- /**
- * @param {{action: string, params?: import('core').SerializableObject}} message
- * @param {chrome.runtime.MessageSender} sender
- * @param {(response?: unknown) => void} callback
- * @returns {boolean}
- */
- _onMessage({action, params}, sender, callback) {
- const messageHandler = this._messageHandlers.get(action);
- if (typeof messageHandler === 'undefined') { return false; }
- return invokeMessageHandler(messageHandler, params, callback, sender);
+ /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
+ _onMessage({action, params}, _sender, callback) {
+ return invokeApiMapHandler(this._apiMap, action, params, [], callback);
}
/**
@@ -284,9 +271,7 @@ export class SearchDisplayController {
this._clipboardMonitor.setPreviousText(selection !== null ? selection.toString().trim() : '');
}
- /**
- * @param {{text: string, animate?: boolean}} details
- */
+ /** @type {import('application').ApiHandler<'searchDisplayControllerUpdateSearchQuery'>} */
_onExternalSearchUpdate({text, animate = true}) {
const options = this._display.getOptions();
if (options === null) { return; }
@@ -548,15 +533,6 @@ export class SearchDisplayController {
}
}
- /**
- * @param {import('core').MessageHandlerMapInit} handlers
- */
- _registerMessageHandlers(handlers) {
- for (const [name, handlerInfo] of handlers) {
- this._messageHandlers.set(name, handlerInfo);
- }
- }
-
/**
* @param {?Element} element
* @returns {boolean}
diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js
index 179ce9a717..36ed8b4358 100644
--- a/ext/js/language/translator.js
+++ b/ext/js/language/translator.js
@@ -982,7 +982,7 @@ export class Translator {
case 'pitch':
{
if (data.reading !== reading) { continue; }
- /** @type {import('dictionary').TermPitch[]} */
+ /** @type {import('dictionary').PitchAccent[]} */
const pitches = [];
for (const {position, tags, nasal, devoice} of data.pitches) {
/** @type {import('dictionary').Tag[]} */
@@ -992,7 +992,13 @@ export class Translator {
}
const nasalPositions = this._toNumberArray(nasal);
const devoicePositions = this._toNumberArray(devoice);
- pitches.push({position, nasalPositions, devoicePositions, tags: tags2});
+ pitches.push({
+ type: 'pitch-accent',
+ position,
+ nasalPositions,
+ devoicePositions,
+ tags: tags2
+ });
}
for (const {pronunciations, headwordIndex} of targets) {
pronunciations.push(this._createTermPronunciation(
@@ -1006,6 +1012,34 @@ export class Translator {
}
}
break;
+ case 'ipa':
+ {
+ if (data.reading !== reading) { continue; }
+ /** @type {import('dictionary').PhoneticTranscription[]} */
+ const phoneticTranscriptions = [];
+ for (const {ipa, tags} of data.transcriptions) {
+ /** @type {import('dictionary').Tag[]} */
+ const tags2 = [];
+ if (Array.isArray(tags)) {
+ tagAggregator.addTags(tags2, dictionary, tags);
+ }
+ phoneticTranscriptions.push({
+ type: 'phonetic-transcription',
+ ipa,
+ tags: tags2
+ });
+ }
+ for (const {pronunciations, headwordIndex} of targets) {
+ pronunciations.push(this._createTermPronunciation(
+ pronunciations.length,
+ headwordIndex,
+ dictionary,
+ dictionaryIndex,
+ dictionaryPriority,
+ phoneticTranscriptions
+ ));
+ }
+ }
}
}
}
@@ -1359,11 +1393,11 @@ export class Translator {
* @param {string} dictionary
* @param {number} dictionaryIndex
* @param {number} dictionaryPriority
- * @param {import('dictionary').TermPitch[]} pitches
+ * @param {import('dictionary').Pronunciation[]} pronunciations
* @returns {import('dictionary').TermPronunciation}
*/
- _createTermPronunciation(index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches) {
- return {index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches};
+ _createTermPronunciation(index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations) {
+ return {index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations};
}
/**
diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js
index 6f357680d0..aea94b652b 100644
--- a/ext/js/pages/settings/anki-controller.js
+++ b/ext/js/pages/settings/anki-controller.js
@@ -145,6 +145,7 @@ export class AnkiController {
'pitch-accents',
'pitch-accent-graphs',
'pitch-accent-positions',
+ 'phonetic-transcriptions',
'reading',
'screenshot',
'search-query',
diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js
index 57725bcbb1..158102393c 100644
--- a/ext/js/templates/sandbox/anki-template-renderer.js
+++ b/ext/js/templates/sandbox/anki-template-renderer.js
@@ -543,12 +543,13 @@ export class AnkiTemplateRenderer {
const [data] = /** @type {[data: import('anki-templates').NoteData]} */ (args);
const {dictionaryEntry} = data;
if (dictionaryEntry.type !== 'term') { return []; }
- const {pronunciations, headwords} = dictionaryEntry;
+ const {pronunciations: termPronunciations, headwords} = dictionaryEntry;
/** @type {Set} */
const categories = new Set();
- for (const {headwordIndex, pitches} of pronunciations) {
+ for (const {headwordIndex, pronunciations} of termPronunciations) {
const {reading, wordClasses} = headwords[headwordIndex];
const isVerbOrAdjective = DictionaryDataUtil.isNonNounVerbOrAdjective(wordClasses);
+ const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent');
for (const {position} of pitches) {
const category = this._japaneseUtil.getPitchCategory(reading, position, isVerbOrAdjective);
if (category !== null) {
diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js
index 2fbe99cc33..621e9cf029 100644
--- a/ext/js/yomitan.js
+++ b/ext/js/yomitan.js
@@ -18,11 +18,14 @@
import {API} from './comm/api.js';
import {CrossFrameAPI} from './comm/cross-frame-api.js';
-import {EventDispatcher, deferPromise, invokeMessageHandler, log} from './core.js';
+import {EventDispatcher, deferPromise, log} from './core.js';
+import {createApiMap, invokeApiMapHandler} from './core/api-map.js';
import {ExtensionError} from './core/extension-error.js';
-// Set up chrome alias if it's not available (Edge Legacy)
-if ((() => {
+/**
+ * @returns {boolean}
+ */
+function checkChromeNotAvailable() {
let hasChrome = false;
let hasBrowser = false;
try {
@@ -36,7 +39,10 @@ if ((() => {
// NOP
}
return (hasBrowser && !hasChrome);
-})()) {
+}
+
+// Set up chrome alias if it's not available (Edge Legacy)
+if (checkChromeNotAvailable()) {
// @ts-expect-error - objects should have roughly the same interface
// eslint-disable-next-line no-global-assign
chrome = browser;
@@ -90,15 +96,15 @@ export class Yomitan extends EventDispatcher {
this._isBackendReadyPromiseResolve = resolve;
/* eslint-disable no-multi-spaces */
- /** @type {import('core').MessageHandlerMap} */
- this._messageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([
- ['Yomitan.isReady', this._onMessageIsReady.bind(this)],
- ['Yomitan.backendReady', this._onMessageBackendReady.bind(this)],
- ['Yomitan.getUrl', this._onMessageGetUrl.bind(this)],
- ['Yomitan.optionsUpdated', this._onMessageOptionsUpdated.bind(this)],
- ['Yomitan.databaseUpdated', this._onMessageDatabaseUpdated.bind(this)],
- ['Yomitan.zoomChanged', this._onMessageZoomChanged.bind(this)]
- ]));
+ /** @type {import('application').ApiMap} */
+ this._apiMap = createApiMap([
+ ['applicationIsReady', this._onMessageIsReady.bind(this)],
+ ['applicationBackendReady', this._onMessageBackendReady.bind(this)],
+ ['applicationGetUrl', this._onMessageGetUrl.bind(this)],
+ ['applicationOptionsUpdated', this._onMessageOptionsUpdated.bind(this)],
+ ['applicationDatabaseUpdated', this._onMessageDatabaseUpdated.bind(this)],
+ ['applicationZoomChanged', this._onMessageZoomChanged.bind(this)]
+ ]);
/* eslint-enable no-multi-spaces */
}
@@ -166,7 +172,7 @@ export class Yomitan extends EventDispatcher {
*/
ready() {
this._isReady = true;
- this.sendMessage({action: 'yomitanReady'});
+ this.sendMessage({action: 'applicationReady'});
}
/**
@@ -178,6 +184,7 @@ export class Yomitan extends EventDispatcher {
return this._extensionUrlBase !== null && url.startsWith(this._extensionUrlBase);
}
+ // TODO : this function needs type safety
/**
* Runs `chrome.runtime.sendMessage()` with additional exception handling events.
* @param {import('extension').ChromeRuntimeSendMessageArgs} args The arguments to be passed to `chrome.runtime.sendMessage()`.
@@ -216,55 +223,41 @@ export class Yomitan extends EventDispatcher {
return location.href;
}
- /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
- _onMessage({action, params}, sender, callback) {
- const messageHandler = this._messageHandlers.get(action);
- if (typeof messageHandler === 'undefined') { return false; }
- return invokeMessageHandler(messageHandler, params, callback, sender);
+ /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
+ _onMessage({action, params}, _sender, callback) {
+ return invokeApiMapHandler(this._apiMap, action, params, [], callback);
}
- /**
- * @returns {boolean}
- */
+ /** @type {import('application').ApiHandler<'applicationIsReady'>} */
_onMessageIsReady() {
return this._isReady;
}
- /**
- * @returns {void}
- */
+ /** @type {import('application').ApiHandler<'applicationBackendReady'>} */
_onMessageBackendReady() {
if (this._isBackendReadyPromiseResolve === null) { return; }
this._isBackendReadyPromiseResolve();
this._isBackendReadyPromiseResolve = null;
}
- /**
- * @returns {{url: string}}
- */
+ /** @type {import('application').ApiHandler<'applicationGetUrl'>} */
_onMessageGetUrl() {
return {url: this._getUrl()};
}
- /**
- * @param {{source: string}} params
- */
+ /** @type {import('application').ApiHandler<'applicationOptionsUpdated'>} */
_onMessageOptionsUpdated({source}) {
if (source !== 'background') {
this.trigger('optionsUpdated', {source});
}
}
- /**
- * @param {{type: string, cause: string}} params
- */
+ /** @type {import('application').ApiHandler<'applicationDatabaseUpdated'>} */
_onMessageDatabaseUpdated({type, cause}) {
this.trigger('databaseUpdated', {type, cause});
}
- /**
- * @param {{oldZoomFactor: number, newZoomFactor: number}} params
- */
+ /** @type {import('application').ApiHandler<'applicationZoomChanged'>} */
_onMessageZoomChanged({oldZoomFactor, newZoomFactor}) {
this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor});
}
diff --git a/package-lock.json b/package-lock.json
index d7c62cabb0..c8a5bad528 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,6 +54,9 @@
"ts-json-schema-generator": "^1.5.0",
"typescript": "5.3.3",
"vitest": "^0.34.6"
+ },
+ "engines": {
+ "node": ">=20.0.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json
index 49542e39db..86bffc6a0e 100644
--- a/test/data/anki-note-builder-test-results.json
+++ b/test/data/anki-note-builder-test-results.json
@@ -79,6 +79,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -109,6 +110,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -144,6 +146,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -174,6 +177,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -204,6 +208,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -234,6 +239,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -264,6 +270,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -294,6 +301,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -329,6 +337,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -359,6 +368,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -389,6 +399,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -419,6 +430,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -449,6 +461,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -479,6 +492,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -509,6 +523,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -539,6 +554,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -569,6 +585,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -599,6 +616,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -634,6 +652,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "がぞう",
"screenshot": "",
"search-query": "fullQuery",
@@ -669,6 +688,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -704,6 +724,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -739,6 +760,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -769,6 +791,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -804,6 +827,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -834,6 +858,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -869,6 +894,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -899,6 +925,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -929,6 +956,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -959,6 +987,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -994,6 +1023,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1024,6 +1054,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1054,6 +1085,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1084,6 +1116,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1119,6 +1152,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "がぞう",
"screenshot": "",
"search-query": "fullQuery",
@@ -1166,6 +1200,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1196,6 +1231,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1226,6 +1262,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1256,6 +1293,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1286,6 +1324,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1316,6 +1355,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -1351,6 +1391,7 @@
"pitch-accents": "- (うちこむ only) うちこむ
- (うちこむ only) うちこむ
- (ぶちこむ only) ぶちこむ
- (ぶちこむ only) ぶちこむ
",
"pitch-accent-graphs": "- (うちこむ only)
- (うちこむ only)
- (ぶちこむ only)
- (ぶちこむ only)
",
"pitch-accent-positions": "- (うちこむ only) [0]
- (うちこむ only) [3]
- (ぶちこむ only) [0]
- (ぶちこむ only) [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ、ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1381,6 +1422,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ、ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1411,6 +1453,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1441,6 +1484,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -1476,6 +1520,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1506,6 +1551,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1536,6 +1582,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1566,6 +1613,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1596,6 +1644,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1626,6 +1675,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1656,6 +1706,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1686,6 +1737,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1716,6 +1768,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1746,6 +1799,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -1781,6 +1835,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1811,6 +1866,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1841,6 +1897,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1871,6 +1928,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1901,6 +1959,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1931,6 +1990,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1961,6 +2021,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -1991,6 +2052,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2021,6 +2083,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2051,6 +2114,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -2086,6 +2150,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2116,6 +2181,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2146,6 +2212,7 @@
"pitch-accents": "- うちこむ
- うちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2176,6 +2243,7 @@
"pitch-accents": "- ぶちこむ
- ぶちこむ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [0]
- [3]
",
+ "phonetic-transcriptions": "",
"reading": "ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2206,6 +2274,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2236,6 +2305,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2266,6 +2336,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2296,6 +2367,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2326,6 +2398,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "だ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2356,6 +2429,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "ダース",
"screenshot": "",
"search-query": "fullQuery",
@@ -2391,6 +2465,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "よむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2426,6 +2501,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "つよみ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2461,6 +2537,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "よむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2496,6 +2573,7 @@
"pitch-accents": "- (うちこむ only) うちこむ
- (うちこむ only) うちこむ
- (ぶちこむ only) ぶちこむ
- (ぶちこむ only) ぶちこむ
",
"pitch-accent-graphs": "- (うちこむ only)
- (うちこむ only)
- (ぶちこむ only)
- (ぶちこむ only)
",
"pitch-accent-positions": "- (うちこむ only) [0]
- (うちこむ only) [3]
- (ぶちこむ only) [0]
- (ぶちこむ only) [3]
",
+ "phonetic-transcriptions": "",
"reading": "うちこむ、ぶちこむ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2526,6 +2604,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "うつ、ぶつ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2561,6 +2640,7 @@
"pitch-accents": "- おてまえ
- おてまえ
- おてまえ
",
"pitch-accent-graphs": "
",
"pitch-accent-positions": "- [2]
- [2]
- [0]
",
+ "phonetic-transcriptions": "",
"reading": "おてまえ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2596,6 +2676,7 @@
"pitch-accents": "ばんこ゚う",
"pitch-accent-graphs": "",
"pitch-accent-positions": "[3]",
+ "phonetic-transcriptions": "",
"reading": "ばんごう",
"screenshot": "",
"search-query": "fullQuery",
@@ -2631,6 +2712,7 @@
"pitch-accents": "ちゅうこ゚し",
"pitch-accent-graphs": "",
"pitch-accent-positions": "[0]",
+ "phonetic-transcriptions": "",
"reading": "ちゅうごし",
"screenshot": "",
"search-query": "fullQuery",
@@ -2666,6 +2748,7 @@
"pitch-accents": "しょき゚ょう",
"pitch-accent-graphs": "",
"pitch-accent-positions": "[0]",
+ "phonetic-transcriptions": "",
"reading": "しょぎょう",
"screenshot": "",
"search-query": "fullQuery",
@@ -2701,6 +2784,7 @@
"pitch-accents": "どぼくこうじ",
"pitch-accent-graphs": "",
"pitch-accent-positions": "[4]",
+ "phonetic-transcriptions": "",
"reading": "どぼくこうじ",
"screenshot": "",
"search-query": "fullQuery",
@@ -2712,6 +2796,42 @@
}
]
},
+ {
+ "name": "Test pronunciations 6 - phonetic transcriptions",
+ "results": [
+ {
+ "audio": "",
+ "clipboard-image": "",
+ "clipboard-text": "",
+ "cloze-body": "好き",
+ "cloze-prefix": "cloze-prefix",
+ "cloze-suffix": "cloze-suffix",
+ "conjugation": "",
+ "dictionary": "Test Dictionary 2",
+ "document-title": "title",
+ "expression": "好き",
+ "frequencies": "",
+ "furigana": "好き",
+ "furigana-plain": "好[す]き",
+ "glossary": "(adj-na, n, Test Dictionary 2) suki definition
",
+ "glossary-brief": "suki definition
",
+ "glossary-no-dictionary": "(adj-na, n) suki definition
",
+ "part-of-speech": "Unknown",
+ "pitch-accents": "No pitch accent data",
+ "pitch-accent-graphs": "No pitch accent data",
+ "pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
+ "reading": "すき",
+ "screenshot": "",
+ "search-query": "fullQuery",
+ "selection-text": "",
+ "sentence": "cloze-prefix好きcloze-suffix",
+ "sentence-furigana": "cloze-prefix好きcloze-suffix",
+ "tags": "adj-na, n",
+ "url": "url:"
+ }
+ ]
+ },
{
"name": "Structured content test",
"results": [
@@ -2736,6 +2856,7 @@
"pitch-accents": "No pitch accent data",
"pitch-accent-graphs": "No pitch accent data",
"pitch-accent-positions": "No pitch accent data",
+ "phonetic-transcriptions": "",
"reading": "こうぞう",
"screenshot": "",
"search-query": "fullQuery",
diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json
index 14f66d951c..ce4290bdff 100644
--- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json
+++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json
@@ -18,6 +18,7 @@
["中腰", "ちゅうごし", "n", "n", 1, ["chuugoshi definition"], 11, ""],
["所業", "しょぎょう", "n", "n", 1, ["shogyouu definition"], 12, ""],
["土木工事", "どぼくこうじ", "n", "n", 1, ["dobokukouji definition"], 13, ""],
+ ["好き", "すき", "adj-na n", "", 1, ["suki definition"], 14, ""],
[
"内容", "ないよう", "n", "n", 35,
[
diff --git a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json
index 069ea16b30..562966482f 100644
--- a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json
+++ b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json
@@ -108,5 +108,15 @@
{"position": 4, "devoice": 3}
]
}
+ ],
+ [
+ "好き",
+ "ipa",
+ {
+ "reading": "すき",
+ "transcriptions": [
+ {"ipa": "[sɨᵝkʲi]", "tags": ["東京"]}
+ ]
+ }
]
]
\ No newline at end of file
diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json
index 5afb6a6013..ec7f1a1121 100644
--- a/test/data/translator-test-inputs.json
+++ b/test/data/translator-test-inputs.json
@@ -330,6 +330,13 @@
"text": "土木工事",
"options": "default"
},
+ {
+ "name": "Test pronunciations 6 - phonetic transcriptions",
+ "func": "findTerms",
+ "mode": "split",
+ "text": "好き",
+ "options": "default"
+ },
{
"name": "Structured content test",
"func": "findTerms",
diff --git a/test/data/translator-test-results-note-data1.json b/test/data/translator-test-results-note-data1.json
index 34f7c21ab5..1342a63fed 100644
--- a/test/data/translator-test-results-note-data1.json
+++ b/test/data/translator-test-results-note-data1.json
@@ -151,6 +151,7 @@
"uniqueReadings": [],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -313,6 +314,7 @@
"uniqueReadings": [],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -595,6 +597,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -626,6 +629,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -908,6 +912,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -939,6 +944,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -1213,6 +1219,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -1248,6 +1255,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -1517,6 +1525,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -1552,6 +1561,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -1821,6 +1831,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -1856,6 +1867,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -2125,6 +2137,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -2160,6 +2173,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -2433,6 +2447,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -2464,6 +2479,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -2746,6 +2762,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -2777,6 +2794,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -3103,6 +3121,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -3176,6 +3208,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -3497,6 +3535,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -3570,6 +3622,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -3891,6 +3949,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -3964,6 +4036,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -4285,6 +4363,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -4358,6 +4450,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -4629,6 +4727,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -4664,6 +4763,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -4935,6 +5035,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -4970,6 +5071,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -5241,6 +5343,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -5276,6 +5379,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -5547,6 +5651,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -5582,6 +5687,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -5855,6 +5961,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -5886,6 +5993,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -6168,6 +6276,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -6199,6 +6308,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -6320,6 +6430,7 @@
],
"frequencies": [],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -6351,6 +6462,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -6629,6 +6741,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -6660,6 +6773,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -6947,6 +7061,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -6978,6 +7093,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -7252,6 +7368,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -7287,6 +7404,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -7556,6 +7674,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -7591,6 +7710,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -7865,6 +7985,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -7900,6 +8021,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -8169,6 +8291,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -8204,6 +8327,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -8530,6 +8654,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -8603,6 +8741,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -8924,6 +9068,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -8997,6 +9155,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -9268,6 +9432,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -9303,6 +9468,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -9574,6 +9740,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -9609,6 +9776,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -9935,6 +10103,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -10008,6 +10190,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -10329,6 +10517,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -10402,6 +10604,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -10673,6 +10881,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -10708,6 +10917,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -10979,6 +11189,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -11014,6 +11225,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -11135,6 +11347,7 @@
],
"frequencies": [],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -11166,6 +11379,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -11545,6 +11759,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -11600,6 +11828,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -11962,6 +12196,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -12017,6 +12265,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -12329,6 +12583,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -12354,6 +12609,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -12666,6 +12922,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -12691,6 +12948,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -12968,6 +13226,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -12993,6 +13252,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -13279,6 +13539,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -13304,6 +13565,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -13954,6 +14216,32 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ },
+ {
+ "index": 1,
+ "expressionIndex": 1,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 2,
"url": "url:",
"cloze": {
@@ -14042,6 +14330,12 @@
}
],
"pitchCount": 4,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -14586,6 +14880,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 2,
"url": "url:",
"cloze": {
@@ -14612,6 +14907,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -14882,6 +15178,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -14907,6 +15204,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -15186,6 +15484,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -15211,6 +15510,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -15541,6 +15841,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -15614,6 +15928,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -15939,6 +16259,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -16012,6 +16346,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -16337,6 +16677,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -16410,6 +16764,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -16735,6 +17095,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -16808,6 +17182,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -17079,6 +17459,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -17114,6 +17495,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -17385,6 +17767,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -17420,6 +17803,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -17691,6 +18075,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -17726,6 +18111,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -17997,6 +18383,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -18032,6 +18419,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -18305,6 +18693,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -18336,6 +18725,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -18618,6 +19008,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -18649,6 +19040,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -18975,11 +19367,25 @@
]
}
],
- "sourceTermExactMatchCount": 1,
- "url": "url:",
- "cloze": {
- "sentence": "",
- "prefix": "",
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
+ "sourceTermExactMatchCount": 1,
+ "url": "url:",
+ "cloze": {
+ "sentence": "",
+ "prefix": "",
"body": "",
"suffix": ""
},
@@ -19048,6 +19454,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -19369,6 +19781,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -19442,6 +19868,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -19763,6 +20195,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -19836,6 +20282,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -20157,6 +20609,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -20230,6 +20696,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -20501,6 +20973,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -20536,6 +21009,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -20807,6 +21281,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -20842,6 +21317,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -21113,6 +21589,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -21148,6 +21625,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -21419,6 +21897,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -21454,6 +21933,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -21727,6 +22207,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -21758,6 +22239,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -22040,6 +22522,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -22071,6 +22554,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -22397,6 +22881,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -22470,6 +22968,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -22791,6 +23295,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -22864,6 +23382,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -23185,6 +23709,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -23258,6 +23796,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -23579,6 +24123,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -23652,6 +24210,12 @@
}
],
"pitchCount": 2,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -23923,6 +24487,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -23958,6 +24523,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -24229,6 +24795,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -24264,6 +24831,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -24535,6 +25103,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -24570,6 +25139,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -24841,6 +25411,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -24876,6 +25447,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -25149,6 +25721,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -25180,6 +25753,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -25462,6 +26036,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -25493,6 +26068,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -25610,6 +26186,7 @@
],
"frequencies": [],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -25645,6 +26222,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -25760,6 +26338,7 @@
],
"frequencies": [],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -25795,6 +26374,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -25912,6 +26492,7 @@
],
"frequencies": [],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -25947,6 +26528,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -26597,6 +27179,32 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "うちこむ",
+ "phoneticTranscriptions": []
+ },
+ {
+ "index": 1,
+ "expressionIndex": 1,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "打ち込む",
+ "reading": "ぶちこむ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -26685,6 +27293,12 @@
}
],
"pitchCount": 4,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -27229,6 +27843,7 @@
}
],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 0,
"url": "url:",
"cloze": {
@@ -27255,6 +27870,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -27444,6 +28060,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "お手前",
+ "reading": "おてまえ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -27563,6 +28193,12 @@
}
],
"pitchCount": 3,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -27672,6 +28308,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "番号",
+ "reading": "ばんごう",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -27723,6 +28373,12 @@
}
],
"pitchCount": 1,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -27832,6 +28488,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "中腰",
+ "reading": "ちゅうごし",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -27883,6 +28553,12 @@
}
],
"pitchCount": 1,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -27992,6 +28668,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "所業",
+ "reading": "しょぎょう",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -28043,6 +28733,12 @@
}
],
"pitchCount": 1,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -28152,6 +28848,20 @@
]
}
],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "土木工事",
+ "reading": "どぼくこうじ",
+ "phoneticTranscriptions": []
+ }
+ ],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -28203,6 +28913,220 @@
}
],
"pitchCount": 1,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": []
+ }
+ ],
+ "context": {
+ "query": "query",
+ "fullQuery": "fullQuery",
+ "document": {
+ "title": "title"
+ }
+ },
+ "media": {}
+ }
+ ]
+ },
+ {
+ "name": "Test pronunciations 6 - phonetic transcriptions",
+ "noteDataList": [
+ {
+ "marker": "{marker}",
+ "definition": {
+ "type": "term",
+ "id": 20,
+ "source": "好き",
+ "rawSource": "好き",
+ "sourceTerm": "好き",
+ "reasons": [],
+ "score": 1,
+ "isPrimary": true,
+ "sequence": 14,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "dictionaryNames": [
+ "Test Dictionary 2"
+ ],
+ "expression": "好き",
+ "reading": "すき",
+ "expressions": [
+ {
+ "sourceTerm": "好き",
+ "expression": "好き",
+ "reading": "すき",
+ "termTags": [],
+ "frequencies": [],
+ "pitches": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "好き",
+ "reading": "すき",
+ "pitches": []
+ }
+ ],
+ "furiganaSegments": [
+ {
+ "text": "好",
+ "furigana": "す"
+ },
+ {
+ "text": "き",
+ "furigana": ""
+ }
+ ],
+ "termFrequency": "normal",
+ "wordClasses": []
+ }
+ ],
+ "glossary": [
+ "suki definition"
+ ],
+ "definitionTags": [
+ {
+ "name": "adj-na",
+ "category": "default",
+ "notes": "",
+ "order": 0,
+ "score": 0,
+ "dictionary": "Test Dictionary 2",
+ "redundant": false
+ },
+ {
+ "name": "n",
+ "category": "partOfSpeech",
+ "notes": "noun",
+ "order": 0,
+ "score": 0,
+ "dictionary": "Test Dictionary 2",
+ "redundant": false
+ }
+ ],
+ "termTags": [],
+ "frequencies": [],
+ "pitches": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "好き",
+ "reading": "すき",
+ "pitches": []
+ }
+ ],
+ "phoneticTranscriptions": [
+ {
+ "index": 0,
+ "expressionIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryOrder": {
+ "index": 0,
+ "priority": 0
+ },
+ "expression": "好き",
+ "reading": "すき",
+ "phoneticTranscriptions": [
+ {
+ "ipa": "[sɨᵝkʲi]",
+ "tags": [
+ {
+ "name": "東京",
+ "category": "default",
+ "notes": "",
+ "order": 0,
+ "score": 0,
+ "dictionary": "Test Dictionary 2",
+ "redundant": false
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "sourceTermExactMatchCount": 1,
+ "url": "url:",
+ "cloze": {
+ "sentence": "",
+ "prefix": "",
+ "body": "",
+ "suffix": ""
+ },
+ "furiganaSegments": [
+ {
+ "text": "好",
+ "furigana": "す"
+ },
+ {
+ "text": "き",
+ "furigana": ""
+ }
+ ]
+ },
+ "glossaryLayoutMode": "default",
+ "compactTags": false,
+ "group": false,
+ "merge": false,
+ "modeTermKanji": false,
+ "modeTermKana": false,
+ "modeKanji": false,
+ "compactGlossaries": false,
+ "uniqueExpressions": [
+ "好き"
+ ],
+ "uniqueReadings": [
+ "すき"
+ ],
+ "pitches": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "pitches": []
+ }
+ ],
+ "pitchCount": 0,
+ "phoneticTranscriptions": [
+ {
+ "dictionary": "Test Dictionary 2",
+ "phoneticTranscriptions": [
+ {
+ "expressions": [
+ "好き"
+ ],
+ "reading": "すき",
+ "ipa": "[sɨᵝkʲi]",
+ "tags": [
+ {
+ "name": "東京",
+ "category": "default",
+ "order": 0,
+ "score": 0,
+ "content": [],
+ "dictionaries": [
+ "Test Dictionary 2"
+ ],
+ "redundant": false
+ }
+ ],
+ "exclusiveExpressions": [],
+ "exclusiveReadings": []
+ }
+ ]
+ }
+ ],
"context": {
"query": "query",
"fullQuery": "fullQuery",
@@ -28221,7 +29145,7 @@
"marker": "{marker}",
"definition": {
"type": "term",
- "id": 21,
+ "id": 22,
"source": "構造",
"rawSource": "構造",
"sourceTerm": "構造",
@@ -28322,6 +29246,7 @@
],
"frequencies": [],
"pitches": [],
+ "phoneticTranscriptions": [],
"sourceTermExactMatchCount": 1,
"url": "url:",
"cloze": {
@@ -28353,6 +29278,7 @@
],
"pitches": [],
"pitchCount": 0,
+ "phoneticTranscriptions": [],
"context": {
"query": "query",
"fullQuery": "fullQuery",
diff --git a/test/data/translator-test-results.json b/test/data/translator-test-results.json
index 0a7155b885..50d97775c0 100644
--- a/test/data/translator-test-results.json
+++ b/test/data/translator-test-results.json
@@ -1740,14 +1740,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -1927,14 +1929,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -2114,14 +2118,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -2301,14 +2307,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -4637,14 +4645,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -4824,14 +4834,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -5351,14 +5363,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -5538,14 +5552,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -6820,14 +6836,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -7055,14 +7073,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -8194,14 +8214,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -8215,14 +8237,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -9227,14 +9251,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -9418,14 +9444,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -9609,14 +9637,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -9800,14 +9830,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -11000,14 +11032,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -11187,14 +11221,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -11374,14 +11410,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -11561,14 +11599,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -12761,14 +12801,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -12948,14 +12990,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -13135,14 +13179,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -13322,14 +13368,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -15012,14 +15060,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -15033,14 +15083,16 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
"tags": []
},
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [],
"devoicePositions": [],
@@ -15674,8 +15726,9 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 2,
"nasalPositions": [],
"devoicePositions": [],
@@ -15696,6 +15749,7 @@
]
},
{
+ "type": "pitch-accent",
"position": 2,
"nasalPositions": [],
"devoicePositions": [],
@@ -15716,6 +15770,7 @@
]
},
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [],
"devoicePositions": [],
@@ -15820,8 +15875,9 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 3,
"nasalPositions": [
3
@@ -15914,8 +15970,9 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [
3
@@ -16008,8 +16065,9 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 0,
"nasalPositions": [
2
@@ -16102,8 +16160,9 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "pitches": [
+ "pronunciations": [
{
+ "type": "pitch-accent",
"position": 4,
"nasalPositions": [],
"devoicePositions": [
@@ -16118,6 +16177,118 @@
}
]
},
+ {
+ "name": "Test pronunciations 6 - phonetic transcriptions",
+ "originalTextLength": 2,
+ "dictionaryEntries": [
+ {
+ "type": "term",
+ "isPrimary": true,
+ "inflections": [],
+ "score": 1,
+ "frequencyOrder": 0,
+ "dictionaryIndex": 0,
+ "dictionaryPriority": 0,
+ "sourceTermExactMatchCount": 1,
+ "maxTransformedTextLength": 2,
+ "headwords": [
+ {
+ "index": 0,
+ "term": "好き",
+ "reading": "すき",
+ "sources": [
+ {
+ "originalText": "好き",
+ "transformedText": "好き",
+ "deinflectedText": "好き",
+ "matchType": "exact",
+ "matchSource": "term",
+ "isPrimary": true
+ }
+ ],
+ "tags": [],
+ "wordClasses": []
+ }
+ ],
+ "definitions": [
+ {
+ "index": 0,
+ "headwordIndices": [
+ 0
+ ],
+ "dictionary": "Test Dictionary 2",
+ "dictionaryIndex": 0,
+ "dictionaryPriority": 0,
+ "id": 20,
+ "score": 1,
+ "frequencyOrder": 0,
+ "sequences": [
+ 14
+ ],
+ "isPrimary": true,
+ "tags": [
+ {
+ "name": "adj-na",
+ "category": "default",
+ "order": 0,
+ "score": 0,
+ "content": [],
+ "dictionaries": [
+ "Test Dictionary 2"
+ ],
+ "redundant": false
+ },
+ {
+ "name": "n",
+ "category": "partOfSpeech",
+ "order": 0,
+ "score": 0,
+ "content": [
+ "noun"
+ ],
+ "dictionaries": [
+ "Test Dictionary 2"
+ ],
+ "redundant": false
+ }
+ ],
+ "entries": [
+ "suki definition"
+ ]
+ }
+ ],
+ "pronunciations": [
+ {
+ "index": 0,
+ "headwordIndex": 0,
+ "dictionary": "Test Dictionary 2",
+ "dictionaryIndex": 0,
+ "dictionaryPriority": 0,
+ "pronunciations": [
+ {
+ "type": "phonetic-transcription",
+ "ipa": "[sɨᵝkʲi]",
+ "tags": [
+ {
+ "name": "東京",
+ "category": "default",
+ "order": 0,
+ "score": 0,
+ "content": [],
+ "dictionaries": [
+ "Test Dictionary 2"
+ ],
+ "redundant": false
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "frequencies": []
+ }
+ ]
+ },
{
"name": "Structured content test",
"originalTextLength": 2,
@@ -16189,7 +16360,7 @@
"dictionary": "Test Dictionary 2",
"dictionaryIndex": 0,
"dictionaryPriority": 0,
- "id": 21,
+ "id": 22,
"score": 35,
"frequencyOrder": 0,
"sequences": [
diff --git a/test/database.test.js b/test/database.test.js
index 7c3d560698..f5d2c307de 100644
--- a/test/database.test.js
+++ b/test/database.test.js
@@ -164,8 +164,8 @@ async function testDatabase1() {
kanjiMeta: {total: 6, freq: 6},
media: {total: 6},
tagMeta: {total: 15},
- termMeta: {total: 38, freq: 31, pitch: 7},
- terms: {total: 22}
+ termMeta: {total: 39, freq: 31, pitch: 7, ipa: 1},
+ terms: {total: 23}
}
};
@@ -192,8 +192,8 @@ async function testDatabase1() {
true
);
expect(counts).toStrictEqual({
- counts: [{kanji: 2, kanjiMeta: 6, terms: 22, termMeta: 38, tagMeta: 15, media: 6}],
- total: {kanji: 2, kanjiMeta: 6, terms: 22, termMeta: 38, tagMeta: 15, media: 6}
+ counts: [{kanji: 2, kanjiMeta: 6, terms: 23, termMeta: 39, tagMeta: 15, media: 6}],
+ total: {kanji: 2, kanjiMeta: 6, terms: 23, termMeta: 39, tagMeta: 15, media: 6}
});
// Test find* functions
diff --git a/test/utilities/anki.js b/test/utilities/anki.js
index 4b73f6b90a..aa6c83d289 100644
--- a/test/utilities/anki.js
+++ b/test/utilities/anki.js
@@ -75,6 +75,7 @@ function getFieldMarkers(type) {
'pitch-accents',
'pitch-accent-graphs',
'pitch-accent-positions',
+ 'phonetic-transcriptions',
'reading',
'screenshot',
'search-query',
diff --git a/types/ext/anki-templates.d.ts b/types/ext/anki-templates.d.ts
index 5c40f40633..098873e622 100644
--- a/types/ext/anki-templates.d.ts
+++ b/types/ext/anki-templates.d.ts
@@ -76,6 +76,7 @@ export type NoteData = {
readonly uniqueReadings: string[];
readonly pitches: PitchGroup[];
readonly pitchCount: number;
+ readonly phoneticTranscriptions: TranscriptionGroup[];
readonly context: Context;
media: Media;
readonly dictionaryEntry: Dictionary.DictionaryEntry;
@@ -97,6 +98,20 @@ export type Pitch = {
exclusiveReadings: string[];
};
+export type TranscriptionGroup = {
+ dictionary: string;
+ phoneticTranscriptions: Transcription[];
+};
+
+export type Transcription = {
+ expressions: string[];
+ reading: string;
+ ipa: string;
+ tags: Dictionary.Tag[];
+ exclusiveExpressions: string[];
+ exclusiveReadings: string[];
+};
+
/**
* For legacy reasons, {@link Pitch} has a custom tag type that resembles {@link Dictionary.Tag}.
*/
@@ -175,7 +190,8 @@ export type TermDictionaryEntry = {
readonly termTags?: Tag[];
readonly definitions?: TermDefinition[];
readonly frequencies: TermFrequency[];
- readonly pitches: TermPronunciation[];
+ readonly pitches: TermPitchAccent[];
+ readonly phoneticTranscriptions: TermPhoneticTranscription[];
sourceTermExactMatchCount: number;
url: string;
readonly cloze: Cloze;
@@ -225,7 +241,7 @@ export type TermFrequency = {
frequency: number | string;
};
-export type TermPronunciation = {
+export type TermPitchAccent = {
index: number;
expressionIndex: number;
dictionary: string;
@@ -235,14 +251,32 @@ export type TermPronunciation = {
};
expression: string;
reading: string;
- readonly pitches: TermPitch[];
+ readonly pitches: PitchAccent[];
};
-export type TermPitch = {
+export type PitchAccent = {
position: number;
tags: Tag[];
};
+export type TermPhoneticTranscription = {
+ index: number;
+ expressionIndex: number;
+ dictionary: string;
+ dictionaryOrder: {
+ index: number;
+ priority: number;
+ };
+ expression: string;
+ reading: string;
+ readonly phoneticTranscriptions: PhoneticTranscription[];
+};
+
+export type PhoneticTranscription = {
+ ipa: string;
+ tags: Tag[];
+};
+
export type TermFrequencyType = DictionaryDataUtil.TermFrequencyType;
export type TermHeadword = {
@@ -251,7 +285,7 @@ export type TermHeadword = {
reading: string;
readonly termTags: Tag[];
readonly frequencies: TermFrequency[];
- readonly pitches: TermPronunciation[];
+ readonly pitches: TermPitchAccent[];
readonly furiganaSegments: FuriganaSegment[];
readonly termFrequency: TermFrequencyType;
wordClasses: string[];
diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts
index ad3aa22cb0..46dfbdc295 100644
--- a/types/ext/api.d.ts
+++ b/types/ext/api.d.ts
@@ -31,6 +31,7 @@ import type * as Settings from './settings';
import type * as SettingsModifications from './settings-modifications';
import type * as Translation from './translation';
import type * as Translator from './translator';
+import type {ApiMessageNoFrameIdAny as ApplicationApiMessageNoFrameIdAny} from './application';
import type {
ApiMap as BaseApiMap,
ApiMapInit as BaseApiMapInit,
@@ -220,15 +221,13 @@ type ApiSurface = {
sendMessageToFrame: {
params: {
frameId: number;
- action: string;
- params?: Core.SerializableObject;
+ message: ApplicationApiMessageNoFrameIdAny;
};
return: boolean;
};
broadcastTab: {
params: {
- action: string;
- params?: Core.SerializableObject;
+ message: ApplicationApiMessageNoFrameIdAny;
};
return: boolean;
};
diff --git a/types/ext/application.d.ts b/types/ext/application.d.ts
new file mode 100644
index 0000000000..ac594abcae
--- /dev/null
+++ b/types/ext/application.d.ts
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 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 type {TokenString} from './core';
+import type {SearchMode} from './display';
+import type {FrameEndpointReadyDetails, FrameEndpointConnectedDetails} from './frame-client';
+import type {DatabaseUpdateType, DatabaseUpdateCause} from './backend';
+import type {
+ ApiMap as BaseApiMap,
+ ApiHandler as BaseApiHandler,
+ ApiParams as BaseApiParams,
+ ApiNames as BaseApiNames,
+ ApiReturn as BaseApiReturn,
+} from './api-map';
+
+export type ApiSurface = {
+ searchDisplayControllerGetMode: {
+ params: void;
+ return: SearchMode;
+ };
+ searchDisplayControllerSetMode: {
+ params: {
+ mode: SearchMode;
+ };
+ return: void;
+ };
+ searchDisplayControllerUpdateSearchQuery: {
+ params: {
+ text: string;
+ animate?: boolean;
+ };
+ return: void;
+ };
+ applicationReady: {
+ params: void;
+ return: void;
+ };
+ applicationIsReady: {
+ params: void;
+ return: boolean;
+ };
+ applicationBackendReady: {
+ params: void;
+ return: void;
+ };
+ applicationGetUrl: {
+ params: void;
+ return: {
+ url: string;
+ };
+ };
+ applicationOptionsUpdated: {
+ params: {
+ source: string;
+ };
+ return: void;
+ };
+ applicationDatabaseUpdated: {
+ params: {
+ type: DatabaseUpdateType;
+ cause: DatabaseUpdateCause;
+ };
+ return: void;
+ };
+ applicationZoomChanged: {
+ params: {
+ oldZoomFactor: number;
+ newZoomFactor: number;
+ };
+ return: void;
+ };
+ frontendRequestReadyBroadcast: {
+ params: {
+ frameId: number;
+ };
+ return: void;
+ };
+ frontendSetAllVisibleOverride: {
+ params: {
+ value: boolean;
+ priority: number;
+ awaitFrame: boolean;
+ };
+ return: TokenString;
+ };
+ frontendClearAllVisibleOverride: {
+ params: {
+ token: TokenString;
+ };
+ return: boolean;
+ };
+ frontendReady: {
+ params: {
+ frameId: number;
+ };
+ return: void;
+ };
+ frameEndpointReady: {
+ params: FrameEndpointReadyDetails;
+ return: void;
+ };
+ frameEndpointConnected: {
+ params: FrameEndpointConnectedDetails;
+ return: void;
+ };
+};
+
+export type ApiParams = BaseApiParams;
+
+export type ApiNames = BaseApiNames;
+
+export type ApiMessageNoFrameId = (
+ ApiParams extends void ?
+ {action: TName, params?: never} :
+ {action: TName, params: ApiParams}
+);
+
+export type ApiMessage = ApiMessageNoFrameId & {
+ /**
+ * The origin frameId that sent this message.
+ * If sent from the backend, this value will be undefined.
+ */
+ frameId?: number;
+};
+
+export type ApiMessageNoFrameIdAny = {[name in ApiNames]: ApiMessageNoFrameId}[ApiNames];
+
+export type ApiMessageAny = {[name in ApiNames]: ApiMessage}[ApiNames];
+
+export type ApiMap = BaseApiMap;
+
+export type ApiHandler = BaseApiHandler;
+
+export type ApiReturn = BaseApiReturn;
diff --git a/types/ext/dictionary-data-util.d.ts b/types/ext/dictionary-data-util.d.ts
index b78e643956..4ab06f11b8 100644
--- a/types/ext/dictionary-data-util.d.ts
+++ b/types/ext/dictionary-data-util.d.ts
@@ -64,21 +64,15 @@ export type KanjiFrequency = {
export type TermFrequencyType = 'popular' | 'rare' | 'normal';
export type GroupedPronunciationInternal = {
+ pronunciation: Dictionary.Pronunciation;
terms: Set;
reading: string;
- position: number;
- nasalPositions: number[];
- devoicePositions: number[];
- tags: Dictionary.Tag[];
};
export type GroupedPronunciation = {
+ pronunciation: Dictionary.Pronunciation;
terms: string[];
reading: string;
- position: number;
- nasalPositions: number[];
- devoicePositions: number[];
- tags: Dictionary.Tag[];
exclusiveTerms: string[];
exclusiveReadings: string[];
};
diff --git a/types/ext/dictionary-data.d.ts b/types/ext/dictionary-data.d.ts
index b194c190cd..0e0edd5c46 100644
--- a/types/ext/dictionary-data.d.ts
+++ b/types/ext/dictionary-data.d.ts
@@ -125,7 +125,7 @@ export type GenericFrequencyData = string | number | {
export type TermMetaArray = TermMeta[];
-export type TermMeta = TermMetaFrequency | TermMetaPitch;
+export type TermMeta = TermMetaFrequency | TermMetaPitch | TermMetaPhonetic;
export type TermMetaFrequencyDataWithReading = {
reading: string;
@@ -154,6 +154,20 @@ export type TermMetaPitch = [
data: TermMetaPitchData,
];
+export type TermMetaPhonetic = [
+ expression: string,
+ mode: 'ipa',
+ data: TermMetaPhoneticData,
+];
+
+export type TermMetaPhoneticData = {
+ reading: string;
+ transcriptions: {
+ ipa: string;
+ tags?: string[];
+ }[];
+};
+
export type KanjiMetaArray = KanjiMeta[];
export type KanjiMeta = KanjiMetaFrequency;
diff --git a/types/ext/dictionary-database.d.ts b/types/ext/dictionary-database.d.ts
index 3202ef6017..3cf68543cf 100644
--- a/types/ext/dictionary-database.d.ts
+++ b/types/ext/dictionary-database.d.ts
@@ -102,7 +102,7 @@ export type Tag = {
dictionary: string;
};
-export type DatabaseTermMeta = DatabaseTermMetaFrequency | DatabaseTermMetaPitch;
+export type DatabaseTermMeta = DatabaseTermMetaFrequency | DatabaseTermMetaPitch | DatabaseTermMetaPhoneticData;
export type DatabaseTermMetaFrequency = {
expression: string;
@@ -118,12 +118,19 @@ export type DatabaseTermMetaPitch = {
dictionary: string;
};
+export type DatabaseTermMetaPhoneticData = {
+ expression: string;
+ mode: 'ipa';
+ data: DictionaryData.TermMetaPhoneticData;
+ dictionary: string;
+};
+
export type TermMetaFrequencyDataWithReading = {
reading: string;
frequency: DictionaryData.GenericFrequencyData;
};
-export type TermMeta = TermMetaFrequency | TermMetaPitch;
+export type TermMeta = TermMetaFrequency | TermMetaPitch | TermMetaPhoneticData;
export type TermMetaType = TermMeta['mode'];
@@ -136,13 +143,21 @@ export type TermMetaFrequency = {
};
export type TermMetaPitch = {
+ mode: 'pitch';
index: number;
term: string;
- mode: 'pitch';
data: DictionaryData.TermMetaPitchData;
dictionary: string;
};
+export type TermMetaPhoneticData = {
+ mode: 'ipa';
+ index: number;
+ term: string;
+ data: DictionaryData.TermMetaPhoneticData;
+ dictionary: string;
+};
+
export type DatabaseKanjiMeta = DatabaseKanjiMetaFrequency;
export type DatabaseKanjiMetaFrequency = {
diff --git a/types/ext/dictionary.d.ts b/types/ext/dictionary.d.ts
index 3e90dec0e1..7c348e7f5d 100644
--- a/types/ext/dictionary.d.ts
+++ b/types/ext/dictionary.d.ts
@@ -365,15 +365,21 @@ export type TermPronunciation = {
*/
dictionaryPriority: number;
/**
- * The pitch accent representations for the term.
+ * The pronunciations for the term.
*/
- pitches: TermPitch[];
+ pronunciations: Pronunciation[];
};
+export type Pronunciation = PitchAccent | PhoneticTranscription;
+
/**
* Pitch accent information for a term, represented as the position of the downstep.
*/
-export type TermPitch = {
+export type PitchAccent = {
+ /**
+ * Type of the pronunciation, for disambiguation between union type members.
+ */
+ type: 'pitch-accent';
/**
* Position of the downstep, as a number of mora.
*/
@@ -392,6 +398,25 @@ export type TermPitch = {
tags: Tag[];
};
+export type PhoneticTranscription = {
+ /**
+ * Type of the pronunciation, for disambiguation between union type members.
+ */
+ type: 'phonetic-transcription';
+ /**
+ * An IPA transcription.
+ */
+ ipa: string;
+ /**
+ * Tags for the IPA transcription.
+ */
+ tags: Tag[];
+};
+
+export type PronunciationType = Pronunciation['type'];
+
+export type PronunciationGeneric = Extract;
+
/**
* Frequency information corresponds to how frequently a term appears in a corpus,
* which can be a number of occurrences or an overall rank.
diff --git a/types/ext/extension.d.ts b/types/ext/extension.d.ts
index 1c86a4ca84..5a24456669 100644
--- a/types/ext/extension.d.ts
+++ b/types/ext/extension.d.ts
@@ -56,16 +56,7 @@ export type ContentOrigin = {
frameId?: number;
};
-export type ChromeRuntimeMessage = {
- action: string;
- params?: Core.SerializableObject;
-};
-
-export type ChromeRuntimeMessageWithFrameId = ChromeRuntimeMessage & {
- frameId?: number;
-};
-
-export type ChromeRuntimeOnMessageCallback = (
+export type ChromeRuntimeOnMessageCallback = (
message: TMessage,
sender: chrome.runtime.MessageSender,
sendResponse: ChromeRuntimeMessageSendResponseFunction,
diff --git a/types/ext/frontend.d.ts b/types/ext/frontend.d.ts
index 73b24dc39e..4cc8d03b2d 100644
--- a/types/ext/frontend.d.ts
+++ b/types/ext/frontend.d.ts
@@ -48,14 +48,6 @@ export type ConstructorDetails = {
export type PageType = 'web' | 'popup' | 'search';
-export type FrontendRequestReadyBroadcastParams = {
- frameId: number;
-};
-
export type GetPopupInfoResult = {
popupId: string | null;
};
-
-export type FrontendReadyDetails = {
- frameId: number;
-};
diff --git a/types/ext/script-manager.d.ts b/types/ext/script-manager.d.ts
index 57b9ee0601..66a5c20fcc 100644
--- a/types/ext/script-manager.d.ts
+++ b/types/ext/script-manager.d.ts
@@ -21,26 +21,12 @@ export type RunAt = 'document_start' | 'document_end' | 'document_idle';
export type RegistrationDetails = {
/** Same as `matches` in the `content_scripts` manifest key. */
matches: string[];
-
- /** Regex match pattern to use as a fallback when native content script registration isn't supported. */
- /** Should be equivalent to `matches`. */
- urlMatches: string;
-
/** Same as `run_at` in the `content_scripts` manifest key. */
runAt: RunAt;
-
- /** Same as `exclude_matches` in the `content_scripts` manifest key. */
- excludeMatches?: string[];
-
- /** Same as `match_about_blank` in the `content_scripts` manifest key. */
- matchAboutBlank: boolean;
-
/** Same as `all_frames` in the `content_scripts` manifest key. */
allFrames: boolean;
-
/** List of CSS paths. */
css?: string[];
-
/** List of script paths. */
js?: string[];
};