Skip to content

OINK 1.5.0 — Read translations side by side

Compare a page with its translation in an opt-in bilingual view, with scroll sync by shared headings.

OINK 1.5.0 adds an opt-in Bilingual View for multilingual sites. Readers, authors, and translators can open a page beside one of its existing translations and keep both panes aligned near the same section. The feature is off by default, so sites that do not enable it keep the exact same rendered pages and outputs as before. This documentation site is now running on OINK 1.5.0 with Bilingual View enabled across its Docs and blog pages—press B or click Open bilingual view at the top of this page to try it right now.

Bilingual View

Enable it with a single site setting:

hugo.yaml
params:
  ui:
    compare: true

Every Docs, Book, and blog page that has a published HTML translation on the same origin then shows Open bilingual view beside the page actions. Readers can also launch Bilingual View from the command palette or press B when keyboard navigation is enabled. The current page stays on the reading-start side while the other pane loads a translation selected from the target-language menu.

Sync Scroll matches shared visible heading IDs and interpolates smoothly between them. Even when one translation is longer or includes an extra subsection, both panes stay aligned section by section. Partial matches use the available anchors and display an approximate-sync note; when two pages share no usable headings, the reader estimates position from overall reading progress. If heading IDs are duplicated or appear in a different order across languages, Sync Scroll turns off automatically while leaving both panes open for independent reading. Keep explicit {#id} anchors aligned across translations, as described in the multilingual guide, for the best experience.

Bilingual View loads the two existing HTML pages inside same-origin frames only when a reader opens it. Each pane keeps its own language, text direction, tabs, tables, diagrams, footnotes, and interactive components while suppressing duplicate shell navigation, search, comments, and analytics. No language-pair pages, new layouts, or extra output formats are generated at build time. Print, Markdown, and feed outputs remain unchanged.

Responsive reading and accessibility

On viewports narrower than 900px, the reader switches to a compact toolbar row and a language switcher bar that displays one retained pane at a time. The switcher bar also holds the target-language menu so pane titles are not repeated. Even on short, high-zoom viewports (320 × 256 CSS pixels, roughly 400% browser zoom), at least 160px of vertical reading space remains visible without horizontal scrolling.

Right-to-left host pages place the current page on the right (the reading-start side) and the target translation on the left. Resizing across the 900px breakpoint moves the target-language menu between the desktop pane header and the mobile switcher bar without losing keyboard focus, menu state, or the active pane. Forced-colors mode highlights the active pane with a clear border, and the B shortcut and aria-keyshortcuts attribute are exposed only when keyboard navigation is enabled and a translation target exists.

Language and analytics boundaries

Individual languages can opt out by setting compare: false under languages.<lang>.params.ui. An opted-out language never displays the Bilingual View button and is never offered as a target language from other pages. Pages without a same-origin translation, unpublished drafts, language-home fallbacks, and translations hosted on external domains are excluded automatically.

Custom scripts added through hooks/head-end.html execute inside each pane frame. Analytics snippets placed there should skip pane loads by checking window.OinkComparePane, as shown in Analytics and SEO. Theme-managed analytics (services.googleAnalytics.id and params.analytics.*) skips panes automatically; when params.ui.compare: true is active, OINK renders Google Analytics through its own pane-aware template rather than Hugo’s built-in google_analytics.html partial.

If your site sends a Content Security Policy, allow same-origin frames with frame-src 'self' and make sure your server does not block same-origin framing with X-Frame-Options: DENY or frame-ancestors 'none'. See the deployment guide for details.

Browser navigation and history

Opening Bilingual View pushes a single browser history entry. Clicking Close, pressing Escape, or using browser Back closes the reader and restores your scroll position and focus on the host page. If an interactive component such as a diagram viewer or expanded table is open inside a pane, Escape closes that inner dialog first before closing Bilingual View.

Reloading the page while Bilingual View is open—or returning to it with browser Forward—reopens the reader on the existing history entry rather than pushing a duplicate record, so a single Back step always returns to regular reading. In-page #anchor links scroll within their pane without adding history entries, links to other pages on the site exit Bilingual View and navigate normally, and external links open outside the frame.

Upgrade

Use Hugo Extended 0.168.0 and Go 1.27.3. Theme styles continue to compile with Hugo’s built-in LibSass.

hugo mod get github.com/pgsty/[email protected]
hugo mod tidy
hugo --environment production --minify --printPathWarnings

Commit the updated go.mod and go.sum. Refresh vendored or copied theme files separately. Keep Hugo’s LibSass deprecation warning visible and review other diagnostics; do not enable --panicOnWarning or HUGO_PANICONWARNING.

Existing content and configuration need no migration. The new interface labels are translated in all 32 catalogs. The upgrade guide covers validation, CSP, analytics, and rollback.