diff options
Diffstat (limited to 'ext/bg')
-rw-r--r-- | ext/bg/data/options-schema.json | 38 | ||||
-rw-r--r-- | ext/bg/js/handlebars.js | 2 | ||||
-rw-r--r-- | ext/bg/js/options.js | 8 | ||||
-rw-r--r-- | ext/bg/js/search.js | 2 | ||||
-rw-r--r-- | ext/bg/js/settings/main.js | 12 | ||||
-rw-r--r-- | ext/bg/js/translator.js | 138 | ||||
-rw-r--r-- | ext/bg/settings.html | 75 |
7 files changed, 244 insertions, 31 deletions
diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index d4bd3c21..a20a0619 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -65,6 +65,7 @@ "general", "audio", "scanning", + "translation", "dictionaries", "parsing", "anki" @@ -350,6 +351,43 @@ } } }, + "translation": { + "type": "object", + "required": [ + "convertHalfWidthCharacters", + "convertNumericCharacters", + "convertAlphabeticCharacters", + "convertHiraganaToKatakana", + "convertKatakanaToHiragana" + ], + "properties": { + "convertHalfWidthCharacters": { + "type": "string", + "enum": ["false", "true", "variant"], + "default": "false" + }, + "convertNumericCharacters": { + "type": "string", + "enum": ["false", "true", "variant"], + "default": "false" + }, + "convertAlphabeticCharacters": { + "type": "string", + "enum": ["false", "true", "variant"], + "default": "false" + }, + "convertHiraganaToKatakana": { + "type": "string", + "enum": ["false", "true", "variant"], + "default": "false" + }, + "convertKatakanaToHiragana": { + "type": "string", + "enum": ["false", "true", "variant"], + "default": "variant" + } + } + }, "dictionaries": { "type": "object", "additionalProperties": { diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js index 6d1581be..62f89ee4 100644 --- a/ext/bg/js/handlebars.js +++ b/ext/bg/js/handlebars.js @@ -61,7 +61,7 @@ function handlebarsFuriganaPlain(options) { function handlebarsKanjiLinks(options) { let result = ''; for (const c of options.fn(this)) { - if (jpIsKanji(c)) { + if (jpIsCharCodeKanji(c.charCodeAt(0))) { result += `<a href="#" class="kanji-link">${c}</a>`; } else { result += c; diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index c2da76b1..120b34af 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -319,6 +319,14 @@ function profileOptionsCreateDefaults() { enableOnSearchPage: true }, + translation: { + convertHalfWidthCharacters: 'false', + convertNumericCharacters: 'false', + convertAlphabeticCharacters: 'false', + convertHiraganaToKatakana: 'false', + convertKatakanaToHiragana: 'variant' + }, + dictionaries: {}, parsing: { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 673f066b..f5c641a8 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -265,7 +265,7 @@ class DisplaySearch extends Display { text !== this.clipboardPreviousText ) { this.clipboardPreviousText = text; - if (jpIsJapaneseText(text)) { + if (jpIsStringPartiallyJapanese(text)) { this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text); window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`); this.onSearchQueryUpdated(this.query.value, true); diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index bed57f7d..b2ac82f9 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -72,6 +72,12 @@ async function formRead(options) { options.scanning.modifier = $('#scan-modifier-key').val(); options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); + options.translation.convertHalfWidthCharacters = $('#translation-convert-half-width-characters').val(); + options.translation.convertNumericCharacters = $('#translation-convert-numeric-characters').val(); + options.translation.convertAlphabeticCharacters = $('#translation-convert-alphabetic-characters').val(); + options.translation.convertHiraganaToKatakana = $('#translation-convert-hiragana-to-katakana').val(); + options.translation.convertKatakanaToHiragana = $('#translation-convert-katakana-to-hiragana').val(); + options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked'); options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked'); options.parsing.termSpacing = $('#parsing-term-spacing').prop('checked'); @@ -141,6 +147,12 @@ async function formWrite(options) { $('#scan-modifier-key').val(options.scanning.modifier); $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); + $('#translation-convert-half-width-characters').val(options.translation.convertHalfWidthCharacters); + $('#translation-convert-numeric-characters').val(options.translation.convertNumericCharacters); + $('#translation-convert-alphabetic-characters').val(options.translation.convertAlphabeticCharacters); + $('#translation-convert-hiragana-to-katakana').val(options.translation.convertHiraganaToKatakana); + $('#translation-convert-katakana-to-hiragana').val(options.translation.convertKatakanaToHiragana); + $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser); $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser); $('#parsing-reading-mode').val(options.parsing.readingMode); diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index b6e9604d..0f89111f 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -151,7 +151,7 @@ class Translator { async findTermsGrouped(text, details, options) { const dictionaries = dictEnabledSet(options); const titles = Object.keys(dictionaries); - const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details); + const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); const definitionsGrouped = dictTermsGroup(definitions, dictionaries); await this.buildTermFrequencies(definitionsGrouped, titles); @@ -169,7 +169,7 @@ class Translator { const dictionaries = dictEnabledSet(options); const secondarySearchTitles = Object.keys(options.dictionaries).filter((dict) => options.dictionaries[dict].allowSecondarySearches); const titles = Object.keys(dictionaries); - const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details); + const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); const {sequencedDefinitions, defaultDefinitions} = await this.getSequencedDefinitions(definitions, options.general.mainDictionary); const definitionsMerged = []; const mergedByTermIndices = new Set(); @@ -206,26 +206,24 @@ class Translator { async findTermsSplit(text, details, options) { const dictionaries = dictEnabledSet(options); const titles = Object.keys(dictionaries); - const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details); + const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); await this.buildTermFrequencies(definitions, titles); return [definitions, length]; } - async findTermsInternal(text, dictionaries, alphanumeric, details) { - if (!alphanumeric && text.length > 0) { - const c = text[0]; - if (!jpIsKana(c) && !jpIsKanji(c)) { - return [[], 0]; - } + async findTermsInternal(text, dictionaries, details, options) { + text = Translator.getSearchableText(text, options); + if (text.length === 0) { + return [[], 0]; } const titles = Object.keys(dictionaries); const deinflections = ( details.wildcard ? await this.findTermWildcard(text, titles, details.wildcard) : - await this.findTermDeinflections(text, titles) + await this.findTermDeinflections(text, titles, options) ); let definitions = []; @@ -240,6 +238,7 @@ class Translator { definitions.push({ source: deinflection.source, + rawSource: deinflection.rawSource, reasons: deinflection.reasons, score: definition.score, id: definition.id, @@ -260,7 +259,7 @@ class Translator { let length = 0; for (const definition of definitions) { - length = Math.max(length, definition.source.length); + length = Math.max(length, definition.rawSource.length); } return [definitions, length]; @@ -274,6 +273,7 @@ class Translator { return [{ source: text, + rawSource: text, term: text, rules: 0, definitions, @@ -281,9 +281,8 @@ class Translator { }]; } - async findTermDeinflections(text, titles) { - const text2 = jpKatakanaToHiragana(text); - const deinflections = (text === text2 ? this.getDeinflections(text) : this.getDeinflections2(text, text2)); + async findTermDeinflections(text, titles, options) { + const deinflections = this.getAllDeinflections(text, options); if (deinflections.length === 0) { return []; @@ -321,30 +320,77 @@ class Translator { return deinflections.filter((e) => e.definitions.length > 0); } - getDeinflections(text) { + getAllDeinflections(text, options) { + const translationOptions = options.translation; + const textOptionVariantArray = [ + Translator.getTextOptionEntryVariants(translationOptions.convertHalfWidthCharacters), + Translator.getTextOptionEntryVariants(translationOptions.convertNumericCharacters), + Translator.getTextOptionEntryVariants(translationOptions.convertAlphabeticCharacters), + Translator.getTextOptionEntryVariants(translationOptions.convertHiraganaToKatakana), + Translator.getTextOptionEntryVariants(translationOptions.convertKatakanaToHiragana) + ]; + const deinflections = []; + const used = new Set(); + for (const [halfWidth, numeric, alphabetic, katakana, hiragana] of Translator.getArrayVariants(textOptionVariantArray)) { + let text2 = text; + let sourceMapping = null; + if (halfWidth) { + if (sourceMapping === null) { sourceMapping = Translator.createTextSourceMapping(text2); } + text2 = jpConvertHalfWidthKanaToFullWidth(text2, sourceMapping); + } + if (numeric) { + text2 = jpConvertNumericTofullWidth(text2); + } + if (alphabetic) { + if (sourceMapping === null) { sourceMapping = Translator.createTextSourceMapping(text2); } + text2 = jpConvertAlphabeticToKana(text2, sourceMapping); + } + if (katakana) { + text2 = jpHiraganaToKatakana(text2); + } + if (hiragana) { + text2 = jpKatakanaToHiragana(text2); + } - for (let i = text.length; i > 0; --i) { - const textSubstring = text.substring(0, i); - deinflections.push(...this.deinflector.deinflect(textSubstring)); + for (let i = text2.length; i > 0; --i) { + const text2Substring = text2.substring(0, i); + if (used.has(text2Substring)) { break; } + used.add(text2Substring); + for (const deinflection of this.deinflector.deinflect(text2Substring)) { + deinflection.rawSource = Translator.getDeinflectionRawSource(text, i, sourceMapping); + deinflections.push(deinflection); + } + } } - return deinflections; } - getDeinflections2(text1, text2) { - const deinflections = []; + static getTextOptionEntryVariants(value) { + switch (value) { + case 'true': return [true]; + case 'variant': return [false, true]; + default: return [false]; + } + } - for (let i = text1.length; i > 0; --i) { - const text1Substring = text1.substring(0, i); - const text2Substring = text2.substring(0, i); - deinflections.push(...this.deinflector.deinflect(text1Substring)); - if (text1Substring !== text2Substring) { - deinflections.push(...this.deinflector.deinflect(text2Substring)); - } + static getDeinflectionRawSource(source, length, sourceMapping) { + if (sourceMapping === null) { + return source.substring(0, length); } - return deinflections; + let result = ''; + let index = 0; + for (let i = 0; i < length; ++i) { + const c = sourceMapping[i]; + result += source.substring(index, index + c); + index += c; + } + return result; + } + + static createTextSourceMapping(text) { + return new Array(text.length).fill(1); } async findKanji(text, options) { @@ -527,4 +573,38 @@ class Translator { const pos = name.indexOf(':'); return (pos >= 0 ? name.substring(0, pos) : name); } + + static *getArrayVariants(arrayVariants) { + const ii = arrayVariants.length; + + let total = 1; + for (let i = 0; i < ii; ++i) { + total *= arrayVariants[i].length; + } + + for (let a = 0; a < total; ++a) { + const variant = []; + let index = a; + for (let i = 0; i < ii; ++i) { + const entryVariants = arrayVariants[i]; + variant.push(entryVariants[index % entryVariants.length]); + index = Math.floor(index / entryVariants.length); + } + yield variant; + } + } + + static getSearchableText(text, options) { + if (!options.scanning.alphanumeric) { + const ii = text.length; + for (let i = 0; i < ii; ++i) { + if (!jpIsCharCodeJapanese(text.charCodeAt(i))) { + text = text.substring(0, i); + break; + } + } + } + + return text; + } } diff --git a/ext/bg/settings.html b/ext/bg/settings.html index f73f79c8..3e06d4b5 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -397,6 +397,81 @@ </div> </div> + <div> + <h3>Translation Options</h3> + + <p class="help-block"> + The following options can be used during the translation process to provide alternate versions of the input text to search for. + This can be helpful when the input text doesn't exactly match the term or expression found in the database. + </p> + + <p class="help-block"> + The conversion options below are listed in the order that the conversions are applied to the input text. + Each conversion has three possible values: + </p> + + <ul class="help-block"> + <li> + <strong>Disabled</strong><br> + This conversion will never be applied to the input text. + </li> + <li> + <strong>Enabled</strong><br> + This conversion will always be applied to the input text. + </li> + <li> + <strong>Use both variants</strong><br> + The translator will check the database for two variations: the raw input text and the converted input text. + When multiple options use variants, the translator will search for combinations of the converted text. + </li> + </ul> + + <div class="form-group"> + <label for="translation-convert-half-width-characters">Convert half width characters to full width <span class="label-light">(ヨミチャン → ヨミチャン)</span></label> + <select class="form-control" id="translation-convert-half-width-characters"> + <option value="false">Disabled</option> + <option value="true">Enabled</option> + <option value="variant">Use both variants</option> + </select> + </div> + + <div class="form-group"> + <label for="translation-convert-numeric-characters">Convert numeric characters to full width <span class="label-light">(1234 → 1234)</span></label> + <select class="form-control" id="translation-convert-numeric-characters"> + <option value="false">Disabled</option> + <option value="true">Enabled</option> + <option value="variant">Use both variants</option> + </select> + </div> + + <div class="form-group"> + <label for="translation-convert-alphabetic-characters">Convert alphabetic characters to hiragana <span class="label-light">(yomichan → よみちゃん)</span></label> + <select class="form-control" id="translation-convert-alphabetic-characters"> + <option value="false">Disabled</option> + <option value="true">Enabled</option> + <option value="variant">Use both variants</option> + </select> + </div> + + <div class="form-group"> + <label for="translation-convert-hiragana-to-katakana">Convert hiragana to katakana <span class="label-light">(よみちゃん → ヨミチャン)</span></label> + <select class="form-control" id="translation-convert-hiragana-to-katakana"> + <option value="false">Disabled</option> + <option value="true">Enabled</option> + <option value="variant">Use both variants</option> + </select> + </div> + + <div class="form-group"> + <label for="translation-convert-katakana-to-hiragana">Convert katakana to hiragana <span class="label-light">(ヨミチャン → よみちゃん)</span></label> + <select class="form-control" id="translation-convert-katakana-to-hiragana"> + <option value="false">Disabled</option> + <option value="true">Enabled</option> + <option value="variant">Use both variants</option> + </select> + </div> + </div> + <div id="popup-content-scanning"> <h3>Popup Content Scanning Options</h3> |