Git Product home page Git Product logo

anki-jrp's Introduction

Japanese Readings and Pitch Accent Add-on

This Anki add-on allows automatically generating readings (furigana) and pitch accent information for an entire field with a single button press. It can also convert any amount of cards in bulk.

The add-on is currently experimental and you'll probably encounter bugs or crashes. Data corruption is extremely unlikely (probably even impossible), the specific notes being changed aside, but you should still make backups, especially when bulk converting notes, and enable Anki's built-in automatic backups if you haven't already.
See below for details on how to report issues.

Getting Started

  1. Make sure you have a supported version of Anki installed. See the AnkiWeb page for further information.
  2. On non-Windows platforms only, install MeCab since only a Windows executable is bundled with the add-on.
  3. Install the full version of the add-on from AnkiWeb.
    Alternatively, download one of the .ankiaddon files from the latest release and manually install it from the add-on menu.
    • The smallest file contains only the add-on itself, which means you will need to supply your own pitch accent/variant data and install MeCab as well as a MeCab dictionary alongside a dictionary for it to use externally.
    • The full file has everything except a MeCab dictionary, but including a MeCab exe for Windows.
    • The ipadic file contains everything the add-on needs out of the box and is probably what most users will want to install initially.
      This is also the version shared on AnkiWeb.
  4. Restart Anki, open the Manage Note Types menu (Ctrl++N) and set up your templates as described here.
  5. Open the add-on's preferences under Tools in Anki's main menu bar, go the Note Types tab, then select and Add all note types you set up in the last step.
    Click Remove MIA/Migaku for any note types you previously used the Migaku Japanese Add-on with to remove the script and styling from that add-on, otherwise this add-on will not work properly.
  6. Save your preferences. You should now be able to preview or review any cards that contain reading/accent syntax with furigana and accent coloring / indicators.

Adding Readings and Pitch Accent Information

Individual

To generate or remove readings and accent info for a single note, select it in the Anki browser, focus the field you want to change and click one of the conversion buttons at the right of the editor toolbar or press the associated shortcut.

Bulk Conversion

The Notes entry in the browser's menu bar contains an action for converting notes in bulk.

Select any notes you want to change, choose the bulk conversion action from the menu bar, adjust the configuration dialog to what you want and click Convert. All notes need to be of the same note type, since it wouldn't be possible to determine the target field otherwise.

With the Default and Migaku conversion types, any notes that already contain reading/accent syntax will be converted directly to the closest equivalent in the target syntax without regenerating (unless the corresponding option is checked), or left unchanged if the existing syntax type is the same as the target.

If you're converting large amounts of notes, such as sentence mining decks containing hundreds or thousands of cards, make sure to back everything up before running the conversion. Exporting the deck(s) with scheduling information (but without media, since the conversion only changes the notes themselves) should be sufficient.

Syntax

The add-on supports its own fully-featured syntax and is also compatible with the syntax from the old Migaku Japanese Add-on.

Default

In the default syntax, readings are written in square brackets with kanji on the left and furigana on the right separated by a |, like [振|ふ]り[仮名|がな].
Words with pitch accent information are enclosed in curly braces that contain the word (including reading tags) itself and accent information after a semicolon: {[受|う]け[入|い]れる;Y4,0}.

The accent information normally consists of a comma-separated list of numbers, each representing the accented mora, or 0 for unaccented (平板) words. The only special cases are unknown accents marked with a ?, which can only occur by converting from Migaku syntax, and split accents like 一目瞭然 (いち↓もく・りょ→うぜん) which are composed of several parts separated by dashes. Each part consists of the number of the accented mora, an @ sign, and the number of moras it applies to. For example: {[一目瞭然|いちもくりょうぜん];2@4-0@4,0}.

An ! and Y can be placed before the actual pitch accent information.
Exclamation marks indicate ambiguous accents, such as for many kana-only words or certain words with multiple accents. The add-on automatically inserts ambiguity marks if it found more than one possible set of accents with the same reading for a word. You should then manually look up the words in question and adjust the accent tags as necessary.
If present, a Y (from 用 as in 活用・用言) will cause all accents other than [0] to be displayed as the kifuku (起伏) pattern. Ys are added to all words identified as 動詞 (verbs) or 形容詞 (i-adjectives) by MeCab.

Base readings for conjugated words can be indicated in two ways, inline like {[行|い][って=く];Y0}, or after the accent(s) like {[来|き]た;Y1|くる}.

Migaku

For backwards compatibility, Migaku-style syntax is also supported, but it has some limitations compared to the new syntax:

  • Only one reading per word is possible, leading to furigana frequently duplicating characters from the word, like この先このさき, 付き合つきあう or 変わり身かわりみ.
  • The base reading of conjugable words must always be included in full, even the part identical to the reading of the conjugated form. This is also true if the base reading and actual reading are the same, so the Migaku version of what would be {[陥|おとしい]れる;Y5,0} in default syntax is 陥[おとしい,おとしいれる;k5,h]れる.
  • Since words are space-separated, regular ASCII spaces can't be represented properly and are replaced with en spaces (U+2002).
  • Ambiguous accents can't be marked and thus won't be highlighted.
  • Split accents can't be accurately represented and will be displayed as unknown.

Migrating from the Migaku Japanese Add-on

Follow the guide to getting started above. You'll need to manually transfer your accent pattern colors to the note type style settings if you want to keep them. The add-ons have different default colors.

You can easily migrate existing notes to the new syntax without losing any manual changes by selecting them in the browser and bulk converting them with the Default conversion type as long as Regenerate contents is disabled. Make sure to back up all notes in case there are bugs in the conversion algorithm.

Reporting Issues

If you encounter a bug or crash while using the add-on please report it. You can either open a GitHub issue in this repository or contact me on Discord, where you can find me in the Refold community servers.

Explain what you were doing when the issue occurred as accurately as possible, ideally with a list of steps to reproduce it. If the problem is related to specific cards, consider including an .apkg export of those cards (NotesExport Notes... in the Anki browser while having the cards in question selected). If there was an error message include its full contents as well, if possible as selectable text.

anki-jrp's People

Contributors

4rgc avatar ben-kerman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

anki-jrp's Issues

Issues with Mecab

I'm using Arch Linux, I've tried both the AUR packages for mecab, I've tried compiling the dictionaries myself but trying to generate syntac for cards always fails.
When I try to bulk convert after compiling mecab myself it says
Mecab error, stopping conversion: invalid line: param.cpp(69) [ifs] no such file or directory: ./dicrc
When I try using the AUR package it says
Mecab error, stopping conversion: invalid line: param.cpp(69) [ifs] no such file or directory: /usr/lib/mecab/dic/ipadic/dicrc
I've tried copying the dicrc included with the add on (which I downloaded from the Anki website) to the mecab directory, but that resulted in the same error.

Accent Audio Implementation?

Hello, I was just wondering if there are any plans to implement the feature of clicking on words to play audio, like Migaku Japanese did. It was a great feature.

The addon menu goes beyond the screen vertically

Please make the main menu and the note type style menu take up less vertical screen space because I can't see the bottom of the menu window, making it unusable. The machine in question is MacBook Air M1 with a resolution of 2560 x 1600. I have to switch my display settings to the most "spacious" screen setting to see the vertically long main menu in full but even then the note type style menu goes out of screen.

Maybe split the menu into 2 columns?

image

Thank you for the addon.

Allow generating into a different field

It would be handy to be able to keep one field as the original input, and generate the readings into a new field.

While duplicating a field is certainly possible, by default Anki doesn't make this too easy.

Thank you for this useful software!

Error when installing Release 0.1 on Anki 2.1.49 (Linux)

I have tried to install the release 0.1 (IPADIC version) from the .ankiaddon file, but it looks like a library is missing.
Here are the debug info:

Anki 2.1.49 (dc80804a) Python 3.8.1 Qt 5.15.1 PyQt 5.15.1
Platform: Linux
Flags: frz=True ao=True sv=2
Add-ons, last update check: 2022-07-20 08:49:15

Caught exception:
Traceback (most recent call last):
  File "aqt/main.py", line 1634, in onAppMsg
  File "aqt/main.py", line 1189, in installAddon
  File "aqt/addons.py", line 1595, in installAddonPackages
  File "aqt/addons.py", line 467, in processPackages
  File "aqt/addons.py", line 402, in install
  File "aqt/addons.py", line 439, in _install
  File "zipfile.py", line 1628, in extract
  File "zipfile.py", line 1698, in _extract_member
  File "zipfile.py", line 1569, in open
  File "zipfile.py", line 817, in __init__
  File "zipfile.py", line 718, in _get_decompressor
  File "zipfile.py", line 695, in _check_compression
RuntimeError: Compression requires the (missing) lzma module

I also try using the source code, here is the traceback at launch:

⁨Traceback (most recent call last):
  File "aqt/addons.py", line 230, in loadAddons
  File "/home/didier/.local/share/Anki2/addons21/anki-jrp/__init__.py", line 1, in <module>
    from . import ankilib
  File "/home/didier/.local/share/Anki2/addons21/anki-jrp/ankilib/__init__.py", line 1, in <module>
    from . import hooks
  File "/home/didier/.local/share/Anki2/addons21/anki-jrp/ankilib/hooks.py", line 4, in <module>
    from . import browser, editor, global_vars, main_menu, updates
  File "/home/didier/.local/share/Anki2/addons21/anki-jrp/ankilib/browser.py", line 16, in <module>
    from . import global_vars as gv
  File "/home/didier/.local/share/Anki2/addons21/anki-jrp/ankilib/global_vars.py", line 3, in <module>
    from lzma import LZMAError
  File "/home/dae/venv/lib/python3.8/site-packages/PyInstaller-4.0.dev0+g2886519-py3.8.egg/PyInstaller/loader/pyimod03_importers.py", line 625, in exec_module
  File "lzma.py", line 27, in <module>
ModuleNotFoundError: No module named '_lzma'

I thought of a conflict with another add-on, but I'm getting the same result with all other add-ons disabled.

for some cards getting an error of cannot, for others it doesn't work

hello,
I followed all the steps in the guide, but for some cards I get this error: "⚠ Error during parsing: Cannot read properties of null (reading 'length')"
and for others the add-on doesn't change anything. I'm working in windows 11.
These are the fields of the card type in my deck:
image
And this is the syntax of the card front:

{{furigana:kanji_furigana}}
<script>(function(){ function every(itr, pre) { for (const e of itr) if (!pre(e)) return false; return true; } function some(itr, pre) { for (const e of itr) if (pre(e)) return true; return false; } function parse_int_exc(value) { const res = parseInt(value); if (isNaN(res)) { throw new ParsingError(`not a valid integer: ${value}`); } return res; } function maketrans(src, tgt) { return (chr) => { const pos = src.indexOf(chr); if (pos < 0) { return chr; } else return tgt[pos]; }; } function translate(val, tr) { const chars = []; for (const c of val) { chars.push(tr(c)); } return chars.join(""); } function parseHtml(text) { const parent = document.createElement("div"); parent.innerHTML = text; return Array.from(parent.childNodes); } const hira = "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖゝゞ"; const kata = "ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヽヾ"; const to_hira_tbl = maketrans(kata, hira); const to_kata_tbl = maketrans(hira, kata); const non_script_chrs = "ー・"; const is_hira_set = new Set(hira + non_script_chrs); const is_kata_set = new Set(kata + non_script_chrs); function is_hira(kana) { return every(kana, c => is_hira_set.has(c)); } function to_hira(kana) { return translate(kana, to_hira_tbl); } function is_kata(kana) { return every(kana, (c) => is_kata_set.has(c)); } function to_kata(kana) { return translate(kana, to_kata_tbl); } function comp_kana(kana, ...args) { const first = to_kata(kana); return every(args, kstr => to_kata(kstr) === first); } const i_dan = ["キ", "ギ", "シ", "ジ", "チ", "ヂ", "ニ", "ヒ", "ビ", "ピ", "ミ", "リ"]; const e_comp = ["イ", "ウ", "キ", "ギ", "ク", "グ", "シ", "ジ", "チ", "ツ", "ニ", "ヒ", "ビ", "ピ", "フ", "ミ", "リ", "ヴ"]; function split_moras(reading, as_hira = false) { const conv_fn = as_hira ? to_hira : to_kata; const kana = to_kata(reading); const moras = []; for (let i = 0; i < kana.length; ++i) { const ck = kana[i]; const nk = i + 1 < kana.length ? kana[i + 1] : null; if (nk !== null) { if (i_dan.includes(ck) && ["ャ", "ュ", "ョ"].includes(nk) || nk === "ヮ" && ["ク", "グ"].includes(ck) || nk === "ァ" && ["ツ", "フ", "ヴ"].includes(ck) || nk === "ィ" && ["ク", "グ", "ス", "ズ", "テ", "ツ", "デ", "フ", "イ", "ウ", "ヴ"].includes(ck) || nk === "ゥ" && ["ト", "ド", "ホ", "ウ"].includes(ck) || nk === "ェ" && e_comp.includes(ck) || nk === "ォ" && ["ク", "グ", "ツ", "フ", "ウ", "ヴ"].includes(ck) || nk === "ャ" && ["フ", "ヴ"].includes(ck) || nk === "ュ" && ["テ", "デ", "フ", "ウ", "ヴ"].includes(ck) || nk === "ョ" && ["フ", "ヴ"].includes(ck)) { moras.push(conv_fn(ck + nk)); ++i; continue; } } moras.push(conv_fn(ck)); } return moras; } function generate_accent_nodes(reading, accents, is_yougen) { function graph_span(text, flat = false) { const span = document.createElement("span"); span.classList.add(flat ? "jrp-graph-bar-unaccented" : "jrp-graph-bar-accented"); span.append(text); return span; } function pattern_class(acc, mora_count, is_yougen) { if (acc === 0) { return "jrp-heiban"; } else if (is_yougen) { return "jrp-kifuku"; } else if (acc === 1) { return "jrp-atamadaka"; } else if (acc === mora_count) { return "jrp-odaka"; } else { return "jrp-nakadaka"; } } function patterns_for(acc, mora_count, is_yougen) { if (acc.value === null) { return ["jrp-unknown"]; } else { const iter = Array.isArray(acc.value) ? acc.value : [[acc.value, mora_count]]; return iter.map(([ds_mora, mc]) => pattern_class(ds_mora, mc, is_yougen)); } } function graph_for(acc, moras, is_yougen) { const graph_div = document.createElement("div"); if (acc.value === null) { const unk_span = document.createElement("span"); unk_span.classList.add("jrp-unknown"); unk_span.append(moras.join("")); graph_div.append(unk_span); return graph_div; } let last_mora = 0; const iter = Array.isArray(acc.value) ? acc.value : [[acc.value, moras.length]]; for (const [i, [ds_mora, mc]] of iter.entries()) { const part_moras = moras.slice(last_mora, last_mora + mc); last_mora += mc; function mora_slice(start, end) { return part_moras.slice(start, end).join(""); } const acc_span = document.createElement("span"); acc_span.classList.add(pattern_class(ds_mora, mc, is_yougen)); if (ds_mora === 1) { acc_span.append(graph_span(part_moras[0]), mora_slice(1)); } else if (ds_mora === 0) { acc_span.append(part_moras[0], graph_span(mora_slice(1), true)); } else { acc_span.append(part_moras[0], graph_span(mora_slice(1, ds_mora)), mora_slice(ds_mora)); } graph_div.append(acc_span); if (i < iter.length - 1) { graph_div.append("・"); } } return graph_div; } function indicator_for(acc, mora_count, is_yougen) { const acc_indicator = document.createElement("div"); acc_indicator.classList.add("jrp-indicator"); if (acc.value === null) { const unk_div = document.createElement("div"); unk_div.classList.add("jrp-unknown"); acc_indicator.append(unk_div); return acc_indicator; } const iter = Array.isArray(acc.value) ? acc.value : [[acc.value, mora_count]]; for (const [ds_mora, mc] of iter) { const acc_div = document.createElement("div"); acc_div.classList.add(pattern_class(ds_mora, mc, is_yougen)); acc_indicator.append(acc_div); } return acc_indicator; } const moras = split_moras(reading); const graph_div = document.createElement("div"); graph_div.classList.add("jrp-graph"); const indicator_div = document.createElement("div"); indicator_div.classList.add("jrp-indicator-container"); for (const acc of accents) { graph_div.appendChild(graph_for(acc, moras, is_yougen)); indicator_div.append(indicator_for(acc, moras.length, is_yougen)); } return [patterns_for(accents[0], moras.length, is_yougen), graph_div, indicator_div]; } class Accent { constructor(value) { this.value = value; } static from_str(val, mora_count = null) { function parse_part(v) { const split = v.split("@"); if (split.length === 1) { return [parse_int_exc(v), null]; } else if (split.length === 2) { return [parse_int_exc(split[0]), parse_int_exc(split[1])]; } else { throw new ParsingError(`invalid accent tag: ${val}`); } } if (val === "?") { return new Accent(null); } const part_strs = val.split("-"); if (part_strs.length > 1) { const parts = part_strs.map(parse_part); if (some(parts.slice(0, -1), ([_, mc]) => mc === null)) { throw new ParsingError(`only the last part of a compound accent can have no @: ${val}`); } const [ds_mora, raw_mc] = parts[parts.length - 1]; if (raw_mc === null) { if (mora_count === null) { throw new ParsingError("mora_count missing in Accent#from_str"); } else { const calc_count = mora_count - parts.slice(0, -1).map(p => p[0]).reduce((p, c) => p + c); parts[parts.length - 1] = [ds_mora, calc_count]; } } return new Accent(parts); } else { return new Accent(parse_int_exc(val)); } } } class Segment { constructor(text, reading = null) { this.text = text; this.reading = reading; if (reading !== null && reading.length > 0 && !comp_kana(text, reading)) { this.reading = reading; } else this.reading = null; } get_reading() { return (this.reading !== null ? this.reading : this.text); } } class Unit { constructor(segments, accents = [], is_yougen = false, uncertain = false, base_reading = null, was_bare = false) { this.segments = segments; this.accents = accents; this.is_yougen = is_yougen; this.uncertain = uncertain; this.base_reading = base_reading; this.was_bare = was_bare; } static bare(segments) { const u = new Unit(segments); u.was_bare = true; return u; } accent_reading() { if (this.base_reading === null) { return this.segments.map(s => s.get_reading()).join(""); } else return this.base_reading; } generate_dom_nodes(bare_empty = true) { const segment_nodes = this.segments.flatMap(s => { if (s.reading === null) { return parseHtml(s.text); } else { const rt = document.createElement("rt"); rt.append(...parseHtml(s.reading)); const ruby = document.createElement("ruby"); ruby.append(...parseHtml(s.text)); ruby.appendChild(rt); return [ruby]; } }); if (bare_empty && this.accents.length === 0) { return segment_nodes; } const text_span = document.createElement("span"); text_span.append(...segment_nodes); const unit_span = document.createElement("span"); unit_span.classList.add("jrp-unit"); unit_span.append(text_span); if (this.accents.length > 0) { const [pat_classes, graph, indicators] = generate_accent_nodes(this.accent_reading(), this.accents, this.is_yougen); unit_span.classList.add(pat_classes[0]); for (const [i, pc] of pat_classes.slice(1).entries()) { unit_span.classList.add(`${pc}-${i + 2}`); } if (this.uncertain) { unit_span.classList.add("jrp-uncertain"); } unit_span.append(indicators, graph); } return [unit_span]; } } class ParsingError extends Error { constructor(msg) { super(msg); } } const nbsp_re = /(?:%nbsp|\x0a)/; function replace_nbsp(val) { return val.replace(nbsp_re, " "); } function read_until(val, idx, stop) { const chars = []; for (let i = idx; i < val.length; ++i) { const c = val[i]; if (stop.includes(c)) { return [i, c, chars.join("")]; } else if (c === "\\") { if (++i < val.length) { chars.push(val[i]); } else throw new ParsingError("backslash at end of input"); } else chars.push(c); } return [val.length, null, chars.join("")]; } const mi_acc_re = /^([hkano])(\d*)$/; function parse_migaku_accents(val, reading) { function convert(tag, moras) { const m = tag.match(mi_acc_re); if (m === null) { return new Accent(null); } const pat_c = m[1]; switch (pat_c) { case "h": return new Accent(0); case "a": return new Accent(1); case "k": case "n": if (m[2].length === 0) { throw new ParsingError(`missing downstep number: ${tag}`); } return new Accent(parse_int_exc(m[2])); case "o": return new Accent(moras); default: throw new ParsingError(`invalid Migaku accent pattern: ${tag}`); } } const moras = split_moras(reading).length; return val.split(",").map(t => convert(t, moras)); } function parse_migaku(value) { let State; (function (State) { State[State["BASE_READING"] = 0] = "BASE_READING"; State[State["ACCENTS"] = 1] = "ACCENTS"; State[State["SUFFIX"] = 2] = "SUFFIX"; })(State || (State = {})); class Parser { constructor(val) { this.val = val; this.pos = 0; } skip_space() { while (this.pos < this.val.length && this.val[this.pos] === " ") { ++this.pos; } } read_text(stop) { const full_stop = stop.concat(["<"]); const parts = []; while (true) { const [stop_pos, stop_c, txt] = read_until(this.val, this.pos, full_stop); parts.push(txt); if (stop_c === null || stop.includes(stop_c)) { this.pos = stop_pos + 1; return [stop_c, parts.join("")]; } else { const [tag_end_pos, tag_end_c, tag_cont] = read_until(this.val, stop_pos, [">"]); if (tag_end_c === null) { throw new ParsingError(`Unclosed HTML tag: ${tag_cont}`); } else { this.pos = tag_end_pos + 1; parts.push(tag_cont); parts.push(">"); } } } } parse_unit() { let state; const [prfx_end_c, prefix] = this.read_text(["[", " "]); if (prfx_end_c === " " || prfx_end_c === null) { return Unit.bare([new Segment(prefix)]); } const [rdng_end_c, prefix_reading] = this.read_text([",", ";", "]"]); switch (rdng_end_c) { case ",": state = State.BASE_READING; break; case ";": state = State.ACCENTS; break; case "]": state = State.SUFFIX; break; default: throw new ParsingError(`unclosed Migaku tag: ${this.val}`); } let base_reading = ""; if (state === State.BASE_READING) { let bsfm_end_c; [bsfm_end_c, base_reading] = this.read_text([";", "]"]); switch (bsfm_end_c) { case ";": state = State.ACCENTS; break; case "]": state = State.SUFFIX; break; default: throw new ParsingError(`unclosed Migaku tag: ${this.val}`); } } let accent_str = ""; if (state === State.ACCENTS) { let acct_end, acct_c; [acct_end, acct_c, accent_str] = read_until(this.val, this.pos, ["]"]); switch (acct_c) { case "]": state = State.SUFFIX; break; default: throw new ParsingError(`closing ] missing: ${this.val}`); } this.pos = acct_end + 1; } let suffix = ""; if (state === State.SUFFIX) { [, suffix] = this.read_text([" "]); } const segments = [new Segment(prefix, prefix_reading)]; if (suffix.length > 0) { segments.push(new Segment(suffix)); } const is_yougen = base_reading.length > 0; let accents; if (accent_str.length > 0) { const reading = is_yougen ? base_reading : (prefix_reading + (suffix.length > 0 ? suffix : "")); accents = parse_migaku_accents(accent_str, reading); } else accents = []; return new Unit(segments, accents, is_yougen, false, is_yougen ? base_reading : null); } execute() { const units = []; while (this.pos < this.val.length) { this.skip_space(); units.push(this.parse_unit()); } return units; } } return new Parser(replace_nbsp(value)).execute(); } function parse_jrp(value) { function parse_segment(val, start_idx) { const [sep_idx, sep_c, seg_text] = read_until(val, start_idx + 1, ["|", "="]); if (sep_c !== null) { const [end_idx, end_c, reading] = read_until(val, sep_idx + 1, ["]"]); if (end_c === null) { throw new ParsingError(`segment is missing closing bracket: ${val}`); } return [end_idx + 1, sep_c === "=" ? [seg_text, reading] : new Segment(seg_text, reading)]; } throw new ParsingError(`invalid segment: ${val}`); } function parse_unit(val, start_idx) { const segments = []; const base_reading_parts = []; let pos = start_idx + 1; let broke_out = false; while (pos < val.length) { let last_c, txt; [pos, last_c, txt] = read_until(val, pos, ["[", ";", "}"]); if (txt.length > 0) { segments.push(new Segment(txt)); base_reading_parts.push(txt); } if (last_c === "[") { let srv; [pos, srv] = parse_segment(val, pos); if (srv instanceof Segment) { segments.push(srv); base_reading_parts.push(srv.get_reading()); } else { const [text, base_reading] = srv; segments.push(new Segment(text)); base_reading_parts.push(base_reading); } } else if (last_c === ";" || last_c === "}") { broke_out = true; break; } } if (!broke_out) { throw new ParsingError(`unclosed unit: ${val}`); } let accent_str = ""; let special_base = null; let uncertain = false; let is_yougen = false; if (val[pos] === ";") { ++pos; if (val[pos] === "!") { uncertain = true; ++pos; } if (val[pos] === "Y") { is_yougen = true; ++pos; } let end_idx, end_c; [end_idx, end_c, accent_str] = read_until(val, pos, ["|", "}"]); if (end_c === null) { throw new ParsingError(`unclosed unit: ${val}`); } if (end_c === "|") { let unit_end_idx, ec; [unit_end_idx, ec, special_base] = read_until(val, end_idx + 1, ["}"]); if (ec !== null) { pos = unit_end_idx; } else throw new ParsingError(`unclosed unit: ${val}`); } else { pos = end_idx; } } let base_reading = base_reading_parts.join(""); if (special_base !== null) { base_reading = special_base; } const unit = new Unit(segments, [], is_yougen, uncertain, base_reading); try { const mora_count = split_moras(unit.accent_reading()).length; unit.accents = accent_str.split(",").map(acc => { return Accent.from_str(acc.trim(), mora_count); }); } catch (e) { throw new ParsingError(`invalid accent: ${val}`); } return [pos + 1, unit]; } value = replace_nbsp(value); const units = []; let free_segments = []; let idx = 0; while (idx < value.length) { let c, text; [idx, c, text] = read_until(value, idx, ["[", "{"]); if (text.length > 0) { free_segments.push(new Segment(text)); } switch (c) { case "{": if (free_segments.length > 0) { units.push(Unit.bare(free_segments)); } free_segments = []; let u; [idx, u] = parse_unit(value, idx); units.push(u); break; case "[": let segment; [idx, segment] = parse_segment(value, idx); if (!(segment instanceof Segment)) { throw new ParsingError(`base form segment outside of unit: ${value}`); } free_segments.push(segment); break; } } if (free_segments.length > 0) { units.push(Unit.bare(free_segments)); } return units; } function generator_settings(attr_val) { const res = {}; let idx = 0; while (idx < attr_val.length) { let stop_c, text; [idx, stop_c, text] = read_until(attr_val, idx, [";"]); const split = text.split(":"); switch (split.length) { case 1: res[split[0]] = true; break; case 2: res[split[0]] = split[1]; break; default: throw new ParsingError(`invalid config attribute: ${attr_val}`); } ++idx; } return res; } function generate() { const root_elements = []; for (const e of document.querySelectorAll("[data-jrp-generate]")) { if (e.parentElement.closest("[data-jrp-generate]") === null) { let deepest_parent = e; while (deepest_parent.childNodes.length === 1 && deepest_parent.firstElementChild !== null) { deepest_parent = deepest_parent.firstElementChild; } root_elements.push(deepest_parent); } } const br_re = //; for (const root of root_elements) { const lines = root.innerHTML.split(br_re); while (root.firstChild !== null) { root.firstChild.remove(); } try { const settings = generator_settings(root.getAttribute("data-jrp-generate")); root.append(...lines.flatMap((line, index) => { const parser = settings["migaku"] ? parse_migaku : parse_jrp; const unit_nodes = parser(line).flatMap(u => { return u.generate_dom_nodes(settings["enclose-empty-units"]); }); return index > 0 ? [document.createElement("br"), ...unit_nodes] : unit_nodes; })); root.normalize(); } catch (e) { root.append(`⚠ Error during parsing: ${e.message}`); } } } generate(); })();</script>

Thank you for the help!

Mecab error, stopping conversion: executable not found

After following all the steps on getting started and doing the Bulk Conversion, I got this error: "Mecab error, stopping conversion: executable not found". I'm on mac, and I have the mecab addon installed, and I have it installed through homebrew

FR: allow highlighting parts of sentence

Hi, I have had the habit of adding a part of my sentence in bold so I cab identify which part I need to be looking at when reviewing quickly.

SmartSelect_20221113-071209_AnkiDroid

This way the part is highlighted when I review.

SmartSelect_20221113-071935_AnkiDroid

It is also very useful for long monolingual definitions where I can highlight just one part of a very long definition that just "clicks".

But these html tags seem to be wiped out when using anki-jrp.

Any ideas for a workaround?

FR: option to only show furigana on hover

A nice feature of the old addon was that you could have accent color without furigana by default; furigana could then be revealed on hover/click.

I mostly don't want any furigana, but always want accent colors.

Unable to install addon in anki

Hi, I was a user of the MIA/migaku tools for a couple of years until about a week ago when I updated my Linux Mint system and my old Anki could not start up any longer.

I came looking for an alternative and was pointed to your project - it looks very promising! But I am having an issue installing the addon.

I downloaded japanese-readings-and-pitch-accent_0.1_ipadic.ankiaddon and when I try to install it in anki's update manager I get this error:

Error
An error occurred. Please start Anki while holding down the shift key, which will temporarily disable the add-ons you have installed.
If the issue only occurs when add-ons are enabled, please use the Tools > Add-ons menu item to disable some add-ons and restart Anki, repeating until you discover the add-on that is causing the problem.
When you've discovered the add-on that is causing the problem, please report the issue on the add-on support site.
Debug info:
Anki 2.1.49 (dc80804a) Python 3.8.1 Qt 5.15.1 PyQt 5.15.1
Platform: Linux
Flags: frz=True ao=True sv=1
Add-ons, last update check: 2022-11-03 17:55:48

Caught exception:
Traceback (most recent call last):
  File "aqt/addons.py", line 893, in onInstallFiles
  File "aqt/addons.py", line 1595, in installAddonPackages
  File "aqt/addons.py", line 467, in processPackages
  File "aqt/addons.py", line 402, in install
  File "aqt/addons.py", line 439, in _install
  File "zipfile.py", line 1628, in extract
  File "zipfile.py", line 1698, in _extract_member
  File "zipfile.py", line 1569, in open
  File "zipfile.py", line 817, in __init__
  File "zipfile.py", line 718, in _get_decompressor
  File "zipfile.py", line 695, in _check_compression
RuntimeError: Compression requires the (missing) lzma module

I'm running anki-2.1.49-linux which I downloaded from the anki site.

I'm not that experienced with python versions but I suspect my python setup is missing some lzma module. I tried googling around but don't know how I could add it - I don't seem to have python 3.8.1 on my system so maybe it came with the anki download.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.