diff options
| -rw-r--r-- | ext/data/schemas/options-schema.json | 1 | ||||
| -rw-r--r-- | ext/js/background/backend.js | 7 | ||||
| -rw-r--r-- | ext/js/comm/api.js | 5 | ||||
| -rw-r--r-- | ext/js/data/anki-note-builder.js | 4 | ||||
| -rw-r--r-- | ext/js/display/display-anki.js | 14 | ||||
| -rw-r--r-- | ext/js/display/display-audio.js | 4 | ||||
| -rw-r--r-- | ext/js/display/display.js | 14 | ||||
| -rw-r--r-- | ext/js/language/language-descriptors.js | 31 | ||||
| -rwxr-xr-x | ext/js/language/languages.js | 4 | ||||
| -rw-r--r-- | ext/js/media/audio-downloader.js | 96 | ||||
| -rw-r--r-- | ext/js/pages/settings/audio-controller.js | 2 | ||||
| -rw-r--r-- | ext/templates-settings.html | 2 | ||||
| -rw-r--r-- | types/ext/anki-note-builder.d.ts | 2 | ||||
| -rw-r--r-- | types/ext/api.d.ts | 1 | ||||
| -rw-r--r-- | types/ext/audio-downloader.d.ts | 28 | ||||
| -rw-r--r-- | types/ext/language-descriptors.d.ts | 1 | ||||
| -rw-r--r-- | types/ext/language.d.ts | 1 | ||||
| -rw-r--r-- | types/ext/settings.d.ts | 2 | 
18 files changed, 170 insertions, 49 deletions
diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index dc230620..84619ab4 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -400,6 +400,7 @@                                                          "jpod101",                                                          "jpod101-alternate",                                                          "jisho", +                                                        "lingua-libre",                                                          "text-to-speech",                                                          "text-to-speech-reading",                                                          "custom", diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index a468a3f6..96e8206b 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -676,8 +676,8 @@ export class Backend {      }      /** @type {import('api').ApiHandler<'getTermAudioInfoList'>} */ -    async _onApiGetTermAudioInfoList({source, term, reading}) { -        return await this._audioDownloader.getTermAudioInfoList(source, term, reading); +    async _onApiGetTermAudioInfoList({source, term, reading, languageSummary}) { +        return await this._audioDownloader.getTermAudioInfoList(source, term, reading, languageSummary);      }      /** @type {import('api').ApiHandler<'sendMessageToFrame'>} */ @@ -2171,7 +2171,7 @@ export class Backend {          const {term, reading} = definitionDetails;          if (term.length === 0 && reading.length === 0) { return null; } -        const {sources, preferredAudioIndex, idleTimeout} = details; +        const {sources, preferredAudioIndex, idleTimeout, languageSummary} = details;          let data;          let contentType;          try { @@ -2181,6 +2181,7 @@ export class Backend {                  term,                  reading,                  idleTimeout, +                languageSummary,              ));          } catch (e) {              const error = this._getAudioDownloadError(e); diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 7a98feb1..952b5bfa 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -147,10 +147,11 @@ export class API {       * @param {import('api').ApiParam<'getTermAudioInfoList', 'source'>} source       * @param {import('api').ApiParam<'getTermAudioInfoList', 'term'>} term       * @param {import('api').ApiParam<'getTermAudioInfoList', 'reading'>} reading +     * @param {import('api').ApiParam<'getTermAudioInfoList', 'languageSummary'>} languageSummary       * @returns {Promise<import('api').ApiReturn<'getTermAudioInfoList'>>}       */ -    getTermAudioInfoList(source, term, reading) { -        return this._invoke('getTermAudioInfoList', {source, term, reading}); +    getTermAudioInfoList(source, term, reading, languageSummary) { +        return this._invoke('getTermAudioInfoList', {source, term, reading, languageSummary});      }      /** diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index 38588439..7f7fe3a7 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -428,8 +428,8 @@ export class AnkiNoteBuilder {          if (injectAudio && dictionaryEntryDetails.type !== 'kanji') {              const audioOptions = mediaOptions.audio;              if (typeof audioOptions === 'object' && audioOptions !== null) { -                const {sources, preferredAudioIndex, idleTimeout} = audioOptions; -                audioDetails = {sources, preferredAudioIndex, idleTimeout}; +                const {sources, preferredAudioIndex, idleTimeout, languageSummary} = audioOptions; +                audioDetails = {sources, preferredAudioIndex, idleTimeout, languageSummary};              }          }          if (injectScreenshot) { diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index fc242549..b3b05408 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -193,7 +193,11 @@ export class DisplayAnki {       */      _onOptionsUpdated({options}) {          const { -            general: {resultOutputMode, glossaryLayoutMode, compactTags}, +            general: { +                resultOutputMode, +                glossaryLayoutMode, +                compactTags, +            },              dictionaries,              anki: {                  tags, @@ -883,7 +887,13 @@ export class DisplayAnki {      _getAnkiNoteMediaAudioDetails(details) {          if (details.type !== 'term') { return null; }          const {sources, preferredAudioIndex} = this._displayAudio.getAnkiNoteMediaAudioDetails(details.term, details.reading); -        return {sources, preferredAudioIndex, idleTimeout: this._audioDownloadIdleTimeout}; +        const languageSummary = this._display.getLanguageSummary(); +        return { +            sources, +            preferredAudioIndex, +            idleTimeout: this._audioDownloadIdleTimeout, +            languageSummary, +        };      }      // View note functions diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index c7e08ffe..0d1ca029 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -57,6 +57,7 @@ export class DisplayAudio {              ['jpod101', 'JapanesePod101'],              ['jpod101-alternate', 'JapanesePod101 (Alternate)'],              ['jisho', 'Jisho.org'], +            ['lingua-libre', 'Lingua Libre'],              ['text-to-speech', 'Text-to-speech'],              ['text-to-speech-reading', 'Text-to-speech (Kana reading)'],              ['custom', 'Custom URL'], @@ -677,7 +678,8 @@ export class DisplayAudio {       */      async _getTermAudioInfoList(source, term, reading) {          const sourceData = this._getSourceData(source); -        const infoList = await this._display.application.api.getTermAudioInfoList(sourceData, term, reading); +        const languageSummary = this._display.getLanguageSummary(); +        const infoList = await this._display.application.api.getTermAudioInfoList(sourceData, term, reading, languageSummary);          return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null}));      } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index f86d7b8c..3d18e416 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -192,6 +192,8 @@ export class Display extends EventDispatcher {          this._onMenuButtonMenuCloseBind = this._onMenuButtonMenuClose.bind(this);          /** @type {ThemeController} */          this._themeController = new ThemeController(document.documentElement); +        /** @type {import('language').LanguageSummary[]} */ +        this._languageSummaries = [];          /* eslint-disable @stylistic/no-multi-spaces */          this._hotkeyHandler.registerActions([ @@ -316,6 +318,8 @@ export class Display extends EventDispatcher {              documentElement.dataset.browser = browser;          } +        this._languageSummaries = await this._application.api.getLanguageSummaries(); +          // Prepare          await this._hotkeyHelpController.prepare(this._application.api);          await this._displayGenerator.prepare(); @@ -398,6 +402,16 @@ export class Display extends EventDispatcher {      }      /** +     * @returns {import('language').LanguageSummary} +     * @throws {Error} +     */ +    getLanguageSummary() { +        if (this._options === null) { throw new Error('Options is null'); } +        const language = this._options.general.language; +        return /** @type {import('language').LanguageSummary} */ (this._languageSummaries.find(({iso}) => iso === language)); +    } + +    /**       * @returns {import('settings').OptionsContext}       */      getOptionsContext() { diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index f9fb4f09..2e8ece55 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -50,6 +50,7 @@ const capitalizationPreprocessors = {  const languageDescriptors = [      {          iso: 'ar', +        iso639_3: 'ara',          name: 'Arabic',          exampleText: 'قَرَأَ',          textPreprocessors: { @@ -58,6 +59,7 @@ const languageDescriptors = [      },      {          iso: 'de', +        iso639_3: 'deu',          name: 'German',          exampleText: 'gelesen',          textPreprocessors: { @@ -68,12 +70,14 @@ const languageDescriptors = [      },      {          iso: 'el', +        iso639_3: 'ell',          name: 'Greek',          exampleText: 'διαβάζω',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'en', +        iso639_3: 'eng',          name: 'English',          exampleText: 'read',          textPreprocessors: capitalizationPreprocessors, @@ -81,6 +85,7 @@ const languageDescriptors = [      },      {          iso: 'es', +        iso639_3: 'spa',          name: 'Spanish',          exampleText: 'leer',          textPreprocessors: capitalizationPreprocessors, @@ -88,6 +93,7 @@ const languageDescriptors = [      },      {          iso: 'fa', +        iso639_3: 'fas',          name: 'Persian',          exampleText: 'خواندن',          textPreprocessors: { @@ -96,18 +102,21 @@ const languageDescriptors = [      },      {          iso: 'fi', +        iso639_3: 'fin',          name: 'Finnish',          exampleText: 'lukea',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'fr', +        iso639_3: 'fra',          name: 'French',          exampleText: 'lire',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'grc', +        iso639_3: 'grc',          name: 'Ancient Greek',          exampleText: 'γράφω',          textPreprocessors: { @@ -117,24 +126,28 @@ const languageDescriptors = [      },      {          iso: 'hu', +        iso639_3: 'hun',          name: 'Hungarian',          exampleText: 'olvasni',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'id', +        iso639_3: 'ind',          name: 'Indonesian',          exampleText: 'membaca',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'it', +        iso639_3: 'ita',          name: 'Italian',          exampleText: 'leggere',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'la', +        iso639_3: 'lat',          name: 'Latin',          exampleText: 'legere',          textPreprocessors: { @@ -145,11 +158,13 @@ const languageDescriptors = [      },      {          iso: 'lo', +        iso639_3: 'lao',          name: 'Lao',          exampleText: 'ອ່ານ',      },      {          iso: 'ja', +        iso639_3: 'jpn',          name: 'Japanese',          exampleText: '読め',          isTextLookupWorthy: isStringPartiallyJapanese, @@ -165,11 +180,13 @@ const languageDescriptors = [      },      {          iso: 'km', +        iso639_3: 'khm',          name: 'Khmer',          exampleText: 'អាន',      },      {          iso: 'ko', +        iso639_3: 'kor',          name: 'Korean',          exampleText: '읽어',          textPreprocessors: { @@ -182,24 +199,28 @@ const languageDescriptors = [      },      {          iso: 'nl', +        iso639_3: 'nld',          name: 'Dutch',          exampleText: 'lezen',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'pl', +        iso639_3: 'pol',          name: 'Polish',          exampleText: 'czytacie',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'pt', +        iso639_3: 'por',          name: 'Portuguese',          exampleText: 'ler',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'ro', +        iso639_3: 'ron',          name: 'Romanian',          exampleText: 'citit',          textPreprocessors: { @@ -209,6 +230,7 @@ const languageDescriptors = [      },      {          iso: 'ru', +        iso639_3: 'rus',          name: 'Russian',          exampleText: 'читать',          textPreprocessors: { @@ -219,6 +241,7 @@ const languageDescriptors = [      },      {          iso: 'sga', +        iso639_3: 'sga',          name: 'Old Irish',          exampleText: 'légaid',          textPreprocessors: { @@ -229,6 +252,7 @@ const languageDescriptors = [      },      {          iso: 'sh', +        iso639_3: 'hbs',          name: 'Serbo-Croatian',          exampleText: 'čitaše',          textPreprocessors: { @@ -238,6 +262,7 @@ const languageDescriptors = [      },      {          iso: 'sq', +        iso639_3: 'sqi',          name: 'Albanian',          exampleText: 'ndihmojme',          textPreprocessors: capitalizationPreprocessors, @@ -245,23 +270,27 @@ const languageDescriptors = [      },      {          iso: 'sv', +        iso639_3: 'swe',          name: 'Swedish',          exampleText: 'läsa',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'th', +        iso639_3: 'tha',          name: 'Thai',          exampleText: 'อ่าน',      },      {          iso: 'tr', +        iso639_3: 'tur',          name: 'Turkish',          exampleText: 'okuyor',          textPreprocessors: capitalizationPreprocessors,      },      {          iso: 'vi', +        iso639_3: 'vie',          name: 'Vietnamese',          exampleText: 'đọc',          textPreprocessors: { @@ -271,11 +300,13 @@ const languageDescriptors = [      },      {          iso: 'yue', +        iso639_3: 'yue',          name: 'Cantonese',          exampleText: '讀',      },      {          iso: 'zh', +        iso639_3: 'zho',          name: 'Chinese',          exampleText: '读',          isTextLookupWorthy: isStringPartiallyChinese, diff --git a/ext/js/language/languages.js b/ext/js/language/languages.js index 7759fda5..96f7a080 100755 --- a/ext/js/language/languages.js +++ b/ext/js/language/languages.js @@ -22,8 +22,8 @@ import {languageDescriptorMap} from './language-descriptors.js';   */  export function getLanguageSummaries() {      const results = []; -    for (const {name, iso, exampleText} of languageDescriptorMap.values()) { -        results.push({name, iso, exampleText}); +    for (const {name, iso, iso639_3, exampleText} of languageDescriptorMap.values()) { +        results.push({name, iso, iso639_3, exampleText});      }      return results;  } diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 2d1bc4ec..d378d043 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -25,6 +25,16 @@ import {NativeSimpleDOMParser} from '../dom/native-simple-dom-parser.js';  import {SimpleDOMParser} from '../dom/simple-dom-parser.js';  import {isStringEntirelyKana} from '../language/ja/japanese.js'; +/** @type {RequestInit} */ +const DEFAULT_REQUEST_INIT_PARAMS = { +    method: 'GET', +    mode: 'cors', +    cache: 'default', +    credentials: 'omit', +    redirect: 'follow', +    referrerPolicy: 'no-referrer', +}; +  export class AudioDownloader {      /**       * @param {RequestBuilder} requestBuilder @@ -39,6 +49,7 @@ export class AudioDownloader {              ['jpod101', this._getInfoJpod101.bind(this)],              ['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)],              ['jisho', this._getInfoJisho.bind(this)], +            ['lingua-libre', this._getInfoLinguaLibre.bind(this)],              ['text-to-speech', this._getInfoTextToSpeech.bind(this)],              ['text-to-speech-reading', this._getInfoTextToSpeechReading.bind(this)],              ['custom', this._getInfoCustom.bind(this)], @@ -50,13 +61,14 @@ export class AudioDownloader {       * @param {import('audio').AudioSourceInfo} source       * @param {string} term       * @param {string} reading +     * @param {import('language').LanguageSummary} languageSummary       * @returns {Promise<import('audio-downloader').Info[]>}       */ -    async getTermAudioInfoList(source, term, reading) { +    async getTermAudioInfoList(source, term, reading, languageSummary) {          const handler = this._getInfoHandlers.get(source.type);          if (typeof handler === 'function') {              try { -                return await handler(term, reading, source); +                return await handler(term, reading, source, languageSummary);              } catch (e) {                  // NOP              } @@ -70,12 +82,13 @@ export class AudioDownloader {       * @param {string} term       * @param {string} reading       * @param {?number} idleTimeout +     * @param {import('language').LanguageSummary} languageSummary       * @returns {Promise<import('audio-downloader').AudioBinaryBase64>}       */ -    async downloadTermAudio(sources, preferredAudioIndex, term, reading, idleTimeout) { +    async downloadTermAudio(sources, preferredAudioIndex, term, reading, idleTimeout, languageSummary) {          const errors = [];          for (const source of sources) { -            let infoList = await this.getTermAudioInfoList(source, term, reading); +            let infoList = await this.getTermAudioInfoList(source, term, reading, languageSummary);              if (typeof preferredAudioIndex === 'number') {                  infoList = (preferredAudioIndex >= 0 && preferredAudioIndex < infoList.length ? [infoList[preferredAudioIndex]] : []);              } @@ -137,12 +150,8 @@ export class AudioDownloader {              vulgar: 'true',          });          const response = await this._requestBuilder.fetchAnonymous(fetchUrl, { +            ...DEFAULT_REQUEST_INIT_PARAMS,              method: 'POST', -            mode: 'cors', -            cache: 'default', -            credentials: 'omit', -            redirect: 'follow', -            referrerPolicy: 'no-referrer',              headers: {                  'Content-Type': 'application/x-www-form-urlencoded',              }, @@ -181,14 +190,7 @@ export class AudioDownloader {      /** @type {import('audio-downloader').GetInfoHandler} */      async _getInfoJisho(term, reading) {          const fetchUrl = `https://jisho.org/search/${term}`; -        const response = await this._requestBuilder.fetchAnonymous(fetchUrl, { -            method: 'GET', -            mode: 'cors', -            cache: 'default', -            credentials: 'omit', -            redirect: 'follow', -            referrerPolicy: 'no-referrer', -        }); +        const response = await this._requestBuilder.fetchAnonymous(fetchUrl, DEFAULT_REQUEST_INIT_PARAMS);          const responseText = await response.text();          const dom = this._createSimpleDOMParser(responseText); @@ -212,6 +214,44 @@ export class AudioDownloader {      }      /** @type {import('audio-downloader').GetInfoHandler} */ +    async _getInfoLinguaLibre(term, _reading, _details, languageSummary) { +        if (typeof languageSummary !== 'object' || languageSummary === null) { +            throw new Error('Invalid arguments'); +        } +        const {iso639_3} = languageSummary; +        const searchCategory = `incategory:"Lingua_Libre_pronunciation-${iso639_3}"`; +        const searchString = `-${term}.wav`; +        const fetchUrl = `https://commons.wikimedia.org/w/api.php?action=query&format=json&list=search&srsearch=intitle:/${searchString}/i+${searchCategory}&srnamespace=6&origin=*`; + +        const response = await this._requestBuilder.fetchAnonymous(fetchUrl, DEFAULT_REQUEST_INIT_PARAMS); + +        /** @type {import('audio-downloader').LinguaLibreLookupResponse} */ +        const lookupResponse = await readResponseJson(response); + +        const lookupResults = lookupResponse.query.search; + +        const fetchFileInfos = lookupResults.map(async ({title}) => { +            const fileInfoURL = `https://commons.wikimedia.org/w/api.php?action=query&format=json&titles=${title}&prop=imageinfo&iiprop=user|url&origin=*`; +            const response2 = await this._requestBuilder.fetchAnonymous(fileInfoURL, DEFAULT_REQUEST_INIT_PARAMS); +            /** @type {import('audio-downloader').LinguaLibreFileResponse} */ +            const fileResponse = await readResponseJson(response2); +            const fileResults = fileResponse.query.pages; +            const results = []; +            for (const page of Object.values(fileResults)) { +                const fileUrl = page.imageinfo[0].url; +                const fileUser = page.imageinfo[0].user; +                const validFilenameTest = new RegExp(`^File:LL-Q\\d+\\s+\\(${iso639_3}\\)-(\\w+ \\()?${fileUser}\\)?-${term}\\.wav$`, 'i'); +                if (validFilenameTest.test(title)) { +                    results.push({type: 'url', url: fileUrl, name: fileUser}); +                } +            } +            return /** @type {import('audio-downloader').Info1[]} */ (results); +        }); + +        return (await Promise.all(fetchFileInfos)).flat(); +    } + +    /** @type {import('audio-downloader').GetInfoHandler} */      async _getInfoTextToSpeech(term, reading, details) {          if (typeof details !== 'object' || details === null) {              throw new Error('Invalid arguments'); @@ -259,14 +299,7 @@ export class AudioDownloader {          }          url = this._getCustomUrl(term, reading, url); -        const response = await this._requestBuilder.fetchAnonymous(url, { -            method: 'GET', -            mode: 'cors', -            cache: 'default', -            credentials: 'omit', -            redirect: 'follow', -            referrerPolicy: 'no-referrer', -        }); +        const response = await this._requestBuilder.fetchAnonymous(url, DEFAULT_REQUEST_INIT_PARAMS);          if (!response.ok) {              throw new Error(`Invalid response: ${response.status}`); @@ -345,12 +378,7 @@ export class AudioDownloader {          }          const response = await this._requestBuilder.fetchAnonymous(url, { -            method: 'GET', -            mode: 'cors', -            cache: 'default', -            credentials: 'omit', -            redirect: 'follow', -            referrerPolicy: 'no-referrer', +            ...DEFAULT_REQUEST_INIT_PARAMS,              signal,          }); @@ -429,12 +457,8 @@ export class AudioDownloader {      async _getCustomAudioListSchema() {          const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');          const response = await fetch(url, { -            method: 'GET', +            ...DEFAULT_REQUEST_INIT_PARAMS,              mode: 'no-cors', -            cache: 'default', -            credentials: 'omit', -            redirect: 'follow', -            referrerPolicy: 'no-referrer',          });          return await readResponseJson(response);      } diff --git a/ext/js/pages/settings/audio-controller.js b/ext/js/pages/settings/audio-controller.js index 34d7adaa..ac79b51a 100644 --- a/ext/js/pages/settings/audio-controller.js +++ b/ext/js/pages/settings/audio-controller.js @@ -221,6 +221,7 @@ export class AudioController extends EventDispatcher {              'jpod101',              'jpod101-alternate',              'jisho', +            'lingua-libre',              'custom',          ];          for (const type of typesAvailable) { @@ -488,6 +489,7 @@ class AudioSourceEntry {              case 'jpod101':              case 'jpod101-alternate':              case 'jisho': +            case 'lingua-libre':              case 'text-to-speech':              case 'text-to-speech-reading':              case 'custom': diff --git a/ext/templates-settings.html b/ext/templates-settings.html index 3112d30f..2443843b 100644 --- a/ext/templates-settings.html +++ b/ext/templates-settings.html @@ -116,6 +116,7 @@              <option value="jpod101">JapanesePod101</option>              <option value="jpod101-alternate">JapanesePod101 (Alternate)</option>              <option value="jisho">Jisho.org</option> +            <option value="lingua-libre">Lingua Libre</option>              <option value="text-to-speech">Text-to-speech</option>              <option value="text-to-speech-reading">Text-to-speech (Kana reading)</option>              <option value="custom">Custom URL</option> @@ -429,6 +430,7 @@          <option value="jpod101">JapanesePod101</option>          <option value="jpod101-alternate">JapanesePod101 (Alternate)</option>          <option value="jisho">Jisho.org</option> +        <option value="lingua-libre">Lingua Libre</option>          <option value="text-to-speech">Text-to-speech</option>          <option value="text-to-speech-reading">Text-to-speech (Kana reading)</option>          <option value="custom">Custom</option> diff --git a/types/ext/anki-note-builder.d.ts b/types/ext/anki-note-builder.d.ts index 510c0687..9174c563 100644 --- a/types/ext/anki-note-builder.d.ts +++ b/types/ext/anki-note-builder.d.ts @@ -24,6 +24,7 @@ import type * as Extension from './extension';  import type * as Settings from './settings';  import type * as TemplateRenderer from './template-renderer';  import type * as Api from './api'; +import type * as Language from './language';  export type CreateNoteDetails = {      dictionaryEntry: Dictionary.DictionaryEntry; @@ -90,6 +91,7 @@ export type AudioMediaOptions = {      sources: Audio.AudioSourceInfo[];      preferredAudioIndex: number | null;      idleTimeout: number | null; +    languageSummary: Language.LanguageSummary;  };  export type MediaOptions = { diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 3a2fc0b8..46be7938 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -219,6 +219,7 @@ type ApiSurface = {              source: Audio.AudioSourceInfo;              term: string;              reading: string; +            languageSummary: Language.LanguageSummary;          };          return: AudioDownloader.Info[];      }; diff --git a/types/ext/audio-downloader.d.ts b/types/ext/audio-downloader.d.ts index a785c185..c7f7ecbe 100644 --- a/types/ext/audio-downloader.d.ts +++ b/types/ext/audio-downloader.d.ts @@ -16,11 +16,13 @@   */  import type * as Audio from './audio'; +import type * as Language from './language';  export type GetInfoHandler = (      term: string,      reading: string,      details?: Audio.AudioSourceInfo, +    languageSummary?: Language.LanguageSummary,  ) => Promise<Info[]>;  export type Info = Info1 | Info2; @@ -52,3 +54,29 @@ export type CustomAudioListSource = {      url: string;      name?: string;  }; + +export type LinguaLibreLookupResponse = { +    query: { +        search: LinguaLibreLookupResult[]; +    }; +}; + +export type LinguaLibreFileResponse = { +    query: { +        pages: Record<string, LinguaLibreFileResult>; +    }; +}; + +export type LinguaLibreLookupResult = { +    title: string; +}; + +export type LinguaLibreFileResult = { +    title: string; +    imageinfo: LinguaLibreFileResultImageInfo[]; +}; + +export type LinguaLibreFileResultImageInfo = { +    url: string; +    user: string; +}; diff --git a/types/ext/language-descriptors.d.ts b/types/ext/language-descriptors.d.ts index d0136d92..52d4a5d2 100644 --- a/types/ext/language-descriptors.d.ts +++ b/types/ext/language-descriptors.d.ts @@ -27,6 +27,7 @@ type LanguageDescriptor<      TTextPostprocessorDescriptor extends TextProcessorDescriptor = Record<string, never>,  > = {      iso: TIso; +    iso639_3: string;      name: string;      exampleText: string;      /** diff --git a/types/ext/language.d.ts b/types/ext/language.d.ts index ea8c0e47..ccc668af 100644 --- a/types/ext/language.d.ts +++ b/types/ext/language.d.ts @@ -63,5 +63,6 @@ export type TextProcessorWithId<T = unknown> = {  export type LanguageSummary = {      name: string;      iso: string; +    iso639_3: string;      exampleText: string;  }; diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index 8183befd..9253efc3 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -383,7 +383,7 @@ export type PopupWindowType = 'normal' | 'popup';  export type PopupWindowState = 'normal' | 'maximized' | 'fullscreen'; -export type AudioSourceType = 'jpod101' | 'jpod101-alternate' | 'jisho' | 'text-to-speech' | 'text-to-speech-reading' | 'custom' | 'custom-json'; +export type AudioSourceType = 'jpod101' | 'jpod101-alternate' | 'jisho' | 'lingua-libre' | 'text-to-speech' | 'text-to-speech-reading' | 'custom' | 'custom-json';  export type TranslationConvertType = 'false' | 'true' | 'variant';  |