Repository navigation
Releases
Releases · Liozhang/z-transplit
Release v1.17.0
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
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
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-phasemousemovelistener 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 theirclientX
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), whilescreenXstays 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 thescreenXtravel 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: 200pxagainst the hardcoded [0.1, 0.9] clamp left about
190px of dead travel at each end; and a swallowedmouseupleft 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:findExistingTranslationinrunPipelinestill decides it. - Translation failures now carry their reason instead of disappearing. Batch
translation never throws — it reports failure only throughfailedIndices—
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").BatchTranslateResultgained anerror
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 — whilebilingualLauncherrecords it
on the same success-clear / failure-set terms as the per-paragraph path. A 429
whoseRetry-Afterasks 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
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
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
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-inTranslated (<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 oneName: valueper
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 as1-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=translationpair 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
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 av*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 thereleasetag 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, thennavigator.platform,
thenZotero.isARM) and fetches Adoptium's aarch64 build, exactly like the
existing Apple Silicon path. - The Linux java locator only tried
/usr/bin/javaand/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
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 -versionsucceeding 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, whichcmd.exedecodes 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.javaresolution 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 readsJAVA_HOMEfirst, then
enumerates the real install locations (Eclipse Adoptium / Java / Microsoft
/ Amazon Corretto / Zulu versioned directories, Android Studio's bundled
jbr,/Library/Java/JavaVirtualMachines/*.jdkon macOS, Homebrew
openjdk@Nfor both Apple Silicon and Intel prefixes), and only then
falls back to PATH. AJAVA_HOMEthat points at a missing directory is
skipped quietly instead of poisoning the result. - macOS:
/usr/bin/javais 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
rawspawn 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
JavaRuntimeManagerandOpenDataLoaderPdfClientshare one java
resolution implementation; the duplicated PATH searches are gone.
Release v1.12.2
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 thezotero-itemmenu
popup, but Zotero'sbuildItemContextMenuaddresses its native entries
positionally (menu.childNodes[i], identical on Zotero 7.0.15 and 10) to
rewrite labels, rewriteoncommand, toggle Fluentdata-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-idwas 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
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.