Skip to content

Releases

Release v1.17.0

Choose a tag to compare

@github-actions github-actions released this 05 Nov 09:22

Fixed

  • Progress popups that stayed on screen after the work was done. Two separate
    causes, both fixed. First, the parsing-component download popup never closed
    itself: it only armed a close timer on the downloader's terminal phases, and
    a fresh download ended at "verifying" — so after the first parse of a slim
    install the popup sat in the corner of the main window until it was clicked.
    The downloader now reports completion after a successful install. Second,
    the bilingual reading popup came back from the dead: the interleave view
    translates blocks lazily as the reader scrolls, so progress reports kept
    arriving long after the entry point had printed its "ready" line and armed
    its close timer, and each late report re-opened the popup the timer had just
    closed — with no new timer, so it stayed open for the rest of the session.
    Reports that arrive after the terminal line are now dropped instead.

Changed

  • Long-running operations now report progress on a single in-place row with
    Zotero's own circular progress indicator filling up as the work proceeds,
    instead of appending one text row per report. This applies to the bilingual
    reading sessions (paragraphs translated), the PDF translation pipeline —
    both the page-by-page phase used by the non-batching engines and the batch
    phase (chunks translated) — the Java runtime download and the parsing
    component download. A row per report used to stack dozens of near-identical
    "x/y" lines: a 56-page document produced 56 of them, which could grow the
    popup taller than the main window.
  • Every checkbox option in the settings pane is now on for a fresh install, so
    a new user gets the full feature set instead of three features shipping
    switched off. "Translate automatically when an item is opened",
    "Use the structure tree to determine reading order" and "Return embedded
    images" now default to on; "Enable text translation", "Record looked-up
    words", "Enable OpenDataLoader parsing" and "Remove the original text of
    translated paragraphs" were already on. Installations that already exist
    keep whatever they had — a default only changes for users who never touched
    that setting. The trade-offs documented in the pane now apply as defaults:
    automatic translation sends one request per opened selection, the structure
    tree adds parse time, and embedded images add parse time and memory (in the
    default overlay rendering they are not drawn, because the source page
    already carries the figures).

Release v1.16.1

Choose a tag to compare

@github-actions github-actions released this 03 Nov 16:01

Changed

  • The pinned OpenDataLoader PDF parsing engine was upgraded from 2.5.0 to
    2.5.17, picking up seventeen upstream patch releases of parsing fixes that
    reach the plugin's analysis: table header rows are now recognized (table
    cells carry a header flag), adjacent text runs merge without phantom
    spaces or stray NUL characters, text hidden in PDF structure-tree
    artifact sequences stays out of the output, and the engine holds less
    memory on long extraction runs. Before landing, the new engine was run
    with the plugin's exact CLI arguments against six sample PDFs spanning
    papers, CJK text, two-column and rotated layouts: the JSON schema is
    compatible (the only additions are optional fields on table rows and
    cells), the extracted body text is byte-identical on every sample, and
    embedded figure extraction is unchanged. The Java requirement is
    unchanged (Java 11+). The slim install fetches the engine on demand by
    its recorded fingerprint, so the new version arrives on the first parse
    after updating — no action needed.

Release v1.16.0

Choose a tag to compare

@github-actions github-actions released this 03 Nov 03:27

Added

  • Paragraph-level linking in the bilingual interleave view. Selecting text on
    either side now highlights the matching block on the other side at the same
    time: select an original block and its translation lights up beneath it,
    select a translation and the original above it answers in kind. The highlight
    is one shared tint plus a 3px bar on the leading edge, the same weight on both
    sides, with no bolding and no size change; reference sections and horizontal
    bars stay out of it. Changing the selection moves the single focus, clicking
    empty space clears it, and keyboard selection behaves exactly like mouse
    selection. Translation-only mode does not link (the original is hidden
    wholesale, so there is no other side to correspond to), and switching modes
    or ending the session strips the temporary attributes again — including the
    ones placed on Zotero's own elements.

Fixed

  • Split-view divider dragging (issue #4). The report said "the divider jumps
    when dragged right, but behaves normally when dragged left", and the real
    cause is different from that description: event delivery was never broken — a
    window-level bubble-phase mousemove listener receives every drag event,
    including while the pointer is over a PDF pane. What broke was the coordinate
    space: once the pointer enters a pane, the events that reach this chrome
    window are retargeted out of the reader's content document and their clientX
    belongs to THAT document's space (measured on a real machine: values jumping
    between 20 and 340 while the pointer actually travelled 640→880 in chrome
    coordinates, an error of 600+ pixels), while screenX stays exact (measured
    638, 663, 688 … 888, one value per pointer step). Dragging right enters the
    right pane, whose content origin sits at chrome ≈632, so the error exceeds
    600px and the divider collapses or even runs backwards; dragging left enters
    the left pane, whose origin is near the chrome origin, so the computed ratio
    looks roughly right. That is the true source of the reported left/right
    asymmetry. The drag now takes the screenX travel as its input and treats the
    left pane's pixel width as the single source of truth, which also removes
    three direction-independent defects: the ratio arithmetic ignored the
    divider's own 5px, so the divider never coincided with the cursor (up to 4.5px
    off); min-width: 200px against the hardcoded [0.1, 0.9] clamp left about
    190px of dead travel at each end; and a swallowed mouseup left the drag
    latched on, resizing the panes on every later mouse move.
  • Split comparison opens exactly one view per item. An item holding both an
    original PDF and its translation used to resolve two sources, so the item
    tree's split entry opened two split views stacked in the same tab deck, the
    upper one covering the lower and answering every mouse event. Each item now
    resolves at most one source PDF: the item's own PDF when it is one (the user
    picked the file explicitly), otherwise a child attachment, preferring a
    non-translation — the translation is the right pane's job — and taking the
    newest, degrading to the newest when the item holds only translations.
    Multi-select still opens one view per item. The right pane's choice is
    unchanged: findExistingTranslation in runPipeline still decides it.
  • Translation failures now carry their reason instead of disappearing. Batch
    translation never throws — it reports failure only through failedIndices —
    and the engine's reason was dropped inside the batch function, so the
    bilingual progress line could only show "Unknown error" (reproduced on a real
    machine: an AI batch hit 429, all 95 paragraphs failed, and the progress line
    said nothing but "Unknown error"). BatchTranslateResult gained an error
    field and the per-paragraph fallback's reason now rides along with the
    result — it is the direct cause of that paragraph's failure, so it takes
    precedence over the batch-level reason — while bilingualLauncher records it
    on the same success-clear / failure-set terms as the per-paragraph path. A 429
    whose Retry-After asks for more than 120 seconds now ends in a dedicated
    "the server asked to wait too long" message instead of throwing the raw
    error: the selection panel used to show the whole HTTP 429 response body to
    the user. Once rate limiting is confirmed the batch stops asking for the
    remaining paragraphs instead of firing them one by one (reproduced: 7
    paragraphs all went out within 20ms and all came back 429).

v1.15.0

Choose a tag to compare

@Liozhang Liozhang released this 30 Oct 11:31

Added

  • In-text example sentences for word cards. Every dictionary lookup of a
    single word now also captures the sentence the word appeared in: the PDF
    selection's page position (pageIndex + rects, carried by the reader's
    text-selection popup) locates the page text, line clustering with
    column-aware x scoping finds the word's line, and sentence-boundary
    expansion (reusing the read-aloud sentence splitter) yields the sentence —
    stored on the card as an in-text context with the source item, its title,
    and the jump-back geometry. The same word met in another paper accumulates
    a second context (deduped per source item + sentence, capped at 20, older
    records without the field load unchanged). The word-cards tab renders the
    card's contexts as an "In-text examples" section; each row shows the
    sentence with its source title and page, and clicking it opens — or focuses
    — the document's reader at the captured position via the official
    Reader.open/navigate surface. Extraction failures (scanned pages, exotic
    layouts) degrade to a context-less card; lookups are never blocked by it.

Release v1.14.1

Choose a tag to compare

@github-actions github-actions released this 30 Oct 01:32

Changed

  • The bilingual reading view's translation styling now follows the mode, so
    salience tracks the translation's role. In interleave mode the original
    leads and the translation follows quietly: muted ink (74% opacity — the
    same hue as the original text, so light and dark themes both stay legible),
    one size smaller, and a neutral hairline on the leading edge as a scanning
    anchor; hovering a block restores full ink and adds a barely-there
    background. In translation-only mode the translation IS the body text, so
    it reads at full ink and inherited size with no decoration. The collapsed
    bar of per-paragraph show/hide went from the accent color to neutral gray
    to match. The accent color lives on in transient states only (loading
    placeholder, failure, read-aloud highlight) and in the peek mark, where the
    attention was explicitly requested.

Release v1.14.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 22:58

Added

  • Manual translation in bilingual reading mode (issue #3). Selecting text in
    the reading view shows a "Translate this paragraph" pill centered below
    the selection; clicking it translates the block the selection sits in — or
    retranslates it with the cache bypassed when a translation already exists.
    Blocks the session inventory missed (paragraphs the reading view rendered
    late) and containers it skips on purpose (lists, tables) get their
    translation slot injected on the spot, joining the progress count, the
    retry flow and read-aloud like any other block. Reference sections stay
    excluded, exactly like automatic translation.
  • An attachment naming template. The new "Translated attachment naming
    template" field in the preferences pane accepts {lang}, {title} and
    {date} placeholders (for example 译文-{title}-{lang}); leaving it empty
    keeps the built-in Translated (<lang>) · <name> shape. Existing
    translations created with the previous template are no longer detected as
    translations after the template changes, so a re-translation produces a
    fresh attachment instead of silently reusing the old one.
  • Custom HTTP headers for the AI and custom (OpenAI-compatible) engines. The
    new multi-line fields in the preferences pane accept one Name: value per
    line and are sent with every request of that engine; a name that collides
    with a built-in header wins, which is the way to drive gateways that
    require a tenant id or a non-Bearer auth scheme. Invalid lines are rejected
    at the pane and never persisted. Changing the headers produces a different
    cache fingerprint, so affected translations are re-requested instead of
    being served from cache.
  • Per-paragraph translation show/hide in interleave bilingual mode. Clicking
    a translation block collapses it to a thin bar so the original can be read
    on its own; clicking the bar brings the translation back. The state belongs
    to interleave mode only — switching to translation-only mode restores every
    block (there a hidden block would blank the paragraph entirely), and a
    drag-selection release never counts as a click.
  • Page-range translation. A new "Translate page range…" entry in the item
    tree menu asks for pages such as 1-3,7 (single pages, closed ranges and
    mixes; full-width commas and dashes are tolerated; a malformed input is
    rejected outright instead of translating the wrong pages) and translates
    only those pages. Every other page is carried into the output PDF exactly
    as-is — no mask, no text removal, no formula screenshot — so a rotated or
    figure-only page outside the range no longer aborts the run. The
    VLM formula pass only visits selected pages, and the completion summary
    counts only what was translated. Repeated range runs merge into the
    existing translated attachment instead of accumulating copies: both
    artifacts keep the source's full page list in order, so newly translated
    pages replace the same positions in the existing file (the attachment item
    itself is kept, along with its title and source link) and translated pages
    accumulate in the file content across runs. A page-count mismatch, an
    unreadable file or any merge failure falls back to saving a separate
    attachment — the merged bytes are fully built in memory before anything
    touches the disk, so the existing translation is never at risk.
  • A glossary. The new "Glossary" field in the preferences pane takes one
    source=translation pair per line (full-width colon accepted, at most 200
    entries) and pins those terms in every request of the AI and custom
    engines — single-paragraph, batched and the per-paragraph fallback alike.
    Terms not listed translate normally. Changing the glossary produces a
    different cache fingerprint, so affected translations are re-requested.
  • An explicit output budget. The new "Output limit per request" field caps
    the tokens one model request may generate (0 = automatic). Reasoning-style
    models spend the budget on hidden thinking and can come back empty on the
    automatic ceiling (observed on StepFun step-3.7-flash); an explicit budget
    replaces the derived value on the single-paragraph, batched and
    per-paragraph paths, and the one escalated retry after an empty completion
    never exceeds it — the budget stays a real ceiling instead of being
    silently enlarged by the retry.

Fixed

  • A source PDF whose file name contains characters Windows forbids (for
    example 论文: v2?.pdf, or a name ending in a dot or space) made the
    translated copy's write fail or produced a fragile name. Source names are
    now sanitized before they feed the translated attachment's disk file name
    and display title: forbidden characters become spaces, trailing dots and
    spaces are stripped, and names longer than 100 characters are truncated.
  • A PDF downloaded as PAPER.PDF (uppercase extension) could end up with an
    empty content type and was then invisible to every "is this a PDF
    attachment" check, so its translation was skipped. The file-name extension
    is now the fallback signal when the content type is missing.

Release v1.13.1

Choose a tag to compare

@github-actions github-actions released this 24 Oct 04:28

Added

  • A continuous-integration workflow (.github/workflows/ci.yml). Every push
    and pull request now runs the full unit suite, the build, and the
    structure / jar-provenance / QA gates on GitHub instead of relying on local
    runs. Pushing a v* tag additionally builds both XPI variants (slim and
    full), generates the Zotero auto-update manifests
    (scripts/make-update-manifests.mjs), creates the GitHub Release with notes
    taken from CHANGELOG.md, and syncs the manifests onto the release tag that
    the plugin's updateURL polls — the step manual releases kept forgetting (the
    manifests sat at 1.8.1 from 1.9.0 through 1.12.2, silently disabling
    auto-update for five releases).

Fixed

  • ARM64 Linux devices (ARM laptops and boards that run Zotero) downloaded an
    x64 Java runtime that could not execute. The platform descriptor treated
    Linux as x64 unconditionally; it now detects the machine architecture
    (system info service, then the XPCOM ABI string, then navigator.platform,
    then Zotero.isARM) and fetches Adoptium's aarch64 build, exactly like the
    existing Apple Silicon path.
  • The Linux java locator only tried /usr/bin/java and /usr/local/bin/java.
    It now enumerates /usr/lib/jvm/* first — where apt, dnf and pacman all
    install JDKs under unpredictable directory names — so an installed JDK is
    found even when PATH was never configured.
  • When the Google engine fell back to the Bing web endpoint, the translation
    appeared with no indication of where it came from. The selection pane now
    shows a note under the result for the rest of the session ("Google was
    unreachable this session; this translation came from the Bing web
    endpoint"), so the provenance of the text is never invisible.
  • The session-sticky Google fallback never cleared until a Zotero restart:
    after one failure, switching engines and back still went straight to Bing.
    Creating a translator for any non-Google engine now clears the marker, so
    returning to Google re-probes the real endpoint.
  • The English README was missing the 1.13.0 features (the AI provider
    presets, the "Test connection" button and the mirror-racing downloads) that
    the Chinese README documented, and neither README explained that the slim
    installer downloads the parsing component and Java runtime on first use.

Release v1.13.0

Choose a tag to compare

@Liozhang Liozhang released this 24 Oct 01:08

Added

  • The managed Java runtime now downloads from a domestic mirror when one is
    reachable. The Adoptium assets API stays the source of the checksum (the
    trust anchor) and is itself reachable from inside mainland China, but the
    archive it points at lives on GitHub release assets, where a direct
    connection effectively never completes — the old code had no fallback, so
    "install the portable JRE" simply hung or failed. The downloader probes all
    sources concurrently (1KB range request), ranks them by latency, and takes
    the fastest, so whichever domestic mirror answers first is the one used.
    Two Adoptium mirrors are registered, Nanjing University's and Tsinghua's,
    with the official binary URL last as the global fallback. Mirror bytes are
    verified against the official SHA-256 before anything is extracted, and a
    mismatch or an unreachable mirror falls through to the next source, so a
    polluted or lagging mirror cannot install anything the official checksum
    does not attest. The mirror path is derived from the package name the API
    returns ({base}/{feature}/jre/{arch}/{os}/{packageName}), so new upstream
    patch releases need no table update. When the API itself is unreachable
    there is no checksum to verify against, and mirrors are deliberately
    skipped in that case rather than installing unverifiable bytes.
  • More sources for the on-demand parsing component (the wheel that carries
    the OpenDataLoader jar). It previously raced the Aliyun PyPI mirror, the
    Tsinghua PyPI mirror, official PyPI, and the GitHub Release; three more
    domestic PyPI mirrors are now registered — Tencent Cloud, Huawei Cloud,
    and USTC — so six sources race concurrently and the fastest wins. All
    five domestic mirrors serve a byte-identical wheel (same SHA-256 as
    official PyPI), which the pinned PROVENANCE fingerprint verifies before
    anything is extracted; a failing or slow source simply loses the race.
    Excluded after testing: USTC's Adoptium mirror (stale — it only carries
    Java 17.0.9 and 404s on current releases) and the Huawei/Tencent/Aliyun
    Adoptium paths (no such path; they serve a portal page), which is why only
    Nanjing University and Tsinghua are registered for the Java runtime.
    Note that Nanjing University's Adoptium mirror has no Apple Silicon
    (aarch64/mac) build, so macOS ARM installs fall through to Tsinghua or the
    official URL — the probe mechanism handles this without a special case.
    Verified end to end on the real network from mainland China
    (ZT_E2E_NETWORK=1): the Nanjing University mirror's Windows x64 zip is
    byte-identical to the official checksum, and the full
    download-extract-probe chain completes in about 15 seconds, with the
    install landing under a Chinese (non-ASCII) data directory path and
    java -version succeeding from it.
  • AI engine provider presets. The engine's three fields (endpoint, key,
    model) used to be free-text-only, so configuring them meant knowing what
    an OpenAI-compatible endpoint is, where to get a key, the exact URL shape,
    and a valid model name — real user feedback was "I don't know how to
    configure the AI API". The settings pane now has a provider dropdown:
    picking DeepSeek, Qwen, Zhipu GLM, Kimi, StepFun, OpenAI, Ollama, or LM
    Studio fills the endpoint and a recommended model name, usually leaving
    only the key to paste (local providers need no key at all). "Custom"
    remains for self-hosted gateways and vLLM. A model name the user edited
    by hand is never overwritten by a preset, and switching to Custom changes
    nothing. The five mainland providers were verified reachable (401/400 =
    endpoint alive, key missing); api.openai.com times out from inside
    mainland China, which is exactly why the presets matter there.
  • A "Test connection" button for the AI engine. It sends one minimal request
    with the values currently in the form (not the saved prefs) and classifies
    the outcome into something actionable: key rejected, endpoint not found,
    rate limited, unreachable (the api.openai.com-from-mainland case), empty
    completion, or success. Before this, a misconfiguration only surfaced as a
    raw HTTP error at translation time, with no way to tell which of the three
    fields was wrong.

Fixed

  • Windows: installing the managed JRE no longer fails when the Zotero data
    directory contains non-ASCII characters (a Chinese Windows username, for
    instance). The install path launches its helper commands through a .bat
    file, which cmd.exe decodes with the console code page — CP936 on a
    Simplified Chinese system — so UTF-8-written Chinese paths arrived as
    mojibake and tar reported "path not found". The batch now starts with
    chcp 65001, switching that cmd session to UTF-8 before the command line
    is read. Measured on Windows: UTF-8 without the codepage switch fails on
    Chinese paths and succeeds on ASCII ones; with it, both succeed. (UTF-16LE
    batch files were tried first and do not work at all under this launch
    path — they are never executed.) The parse path itself moved to
    Subprocess/CreateProcessW back in 1.12.0; this covers the install path.
  • "Cannot invoke java" no longer happens when Java is installed but not
    where the plugin looks. java resolution used to be two drifting copies
    of the same PATH search, each with three hard-coded Windows paths
    (...\Eclipse Adoptium\jdk-17, jdk-11) that never match a real Temurin
    install — the MSI and winget layouts always carry the patch version
    (jdk-17.0.20.1-hotspot). It now lives in one shared module
    (src/core/pdf/javaLocator.ts) that reads JAVA_HOME first, then
    enumerates the real install locations (Eclipse Adoptium / Java / Microsoft
    / Amazon Corretto / Zulu versioned directories, Android Studio's bundled
    jbr, /Library/Java/JavaVirtualMachines/*.jdk on macOS, Homebrew
    openjdk@N for both Apple Silicon and Intel prefixes), and only then
    falls back to PATH. A JAVA_HOME that points at a missing directory is
    skipped quietly instead of poisoning the result.
  • macOS: /usr/bin/java is a system stub that exists and is executable even
    with no JDK installed, and the old resolver handed it out as if it were a
    working runtime — the user then got a cryptic "No Java runtime present"
    from a parse that could never succeed. The stub is now the last candidate,
    after every real install location.
  • The "no java found" error is now a localized, actionable message naming
    both options (install a JDK, or let the plugin install its own portable
    JRE from the settings pane) instead of an English hard-coded string or a
    raw spawn java ENOENT.
  • Mainland China no longer pays a 10-second Google timeout per paragraph.
    The default engine (Google free endpoint) falls back to the keyless Bing
    web endpoint when Google fails — but that fallback used to fire per
    request, so a 50-paragraph paper waited 50 × 10s before the same Bing
    endpoint that answers in under a second. The failure is now sticky for
    the session: after the first failure, later paragraphs go straight to
    Bing. This is reactive (an observed failure), never predictive — it does
    not guess where the user is, the same stance as region's "auto infers
    nothing" — and a Zotero restart probes Google again, so a recovered
    outage is picked up. Measured: translate.googleapis.com times out
    completely from a mainland connection while www.bing.com answers in
    0.45s.
  • The readiness error for an unconfigured AI engine no longer says "missing
    custom endpoint URL" — the message was shared with the custom engine and
    pointed users who picked AI at a setting they never chose. The AI engine
    now has its own message, and the not-ready dialog names the exact settings
    location instead of a vague "complete it in the settings".

Changed

  • JavaRuntimeManager and OpenDataLoaderPdfClient share one java
    resolution implementation; the duplicated PATH searches are gone.

Release v1.12.2

Choose a tag to compare

@Liozhang Liozhang released this 23 Oct 07:58

Fixed

  • Native library item context-menu entries no longer lose their text or
    disappear alongside Z-Transplit's submenu. The plugin used to insert its
    「Z-Transplit」submenu and separator at the TOP of the zotero-itemmenu
    popup, but Zotero's buildItemContextMenu addresses its native entries
    positionally (menu.childNodes[i], identical on Zotero 7.0.15 and 10) to
    rewrite labels, rewrite oncommand, toggle Fluent data-l10n-id
    bindings, and run its hide/show loop. The two inserted nodes shifted every
    index by two: label writes landed on neighbouring items ("Remove from
    Collection" onto "Add to Collection"), data-l10n-id was stripped from
    the wrong node (its label lost for good), entries that should appear
    stayed hidden, and the plugin's own submenu at index 0 had "Show in
    Library" written onto it — the native-label corruption previously worked
    around by re-asserting the label on every popupshowing. The submenu and
    separator are now APPENDED to the end of the popup (separator first):
    native entries keep positions 0-28 and every positional write resolves
    correctly, while the injected nodes sit past the last positional index
    and are never touched by the host. The label re-assertion stays as free
    insurance.

Release v1.12.1

Choose a tag to compare

@Liozhang Liozhang released this 22 Oct 02:25

Fixed

  • Bilingual reading no longer crashes Zotero: every bilingual session crashed xul.dll (exception 0x80000003) 50-70 seconds in on Zotero 10.0.6 - 100% reproducible. The interleave view geometry poll ran on a timer registered on the SDT iframe content window with a chrome-privileged callback; the timer now lives on the chrome main window, and the queue stops itself when the SDT view is torn down.
  • Bilingual entries no longer fail silently: the library menu items now wait for the freshly opened reader to expose its document-structure entry points (up to 10 s), show progress while preparing, and surface the capability disclosure or error text on failure.