Repository navigation
fix(docs): render the share card in the language of the page it belongs to - #190
Merged
Merged
Conversation
…gs to The generated OG card was locale-independent in two compounding places: the card URL was built from `page.slugs` alone, so all seven locales shared one string, and the route resolved the page with no language argument, so that one card carried the English title and description. A Japanese reader sharing a Japanese page got an English preview. Put the locale in the card URL and pass it to the loader. Cards are enumerated over `translatedLocales()` rather than the full locale list, so a page that exists only in English still generates exactly one card, not seven. The card URL resolves through `canonicalLocale`, the same rule `canonical` and `og:url` already use: on a route whose locale has no translation the body being served is the English page, so the card that depicts it is the English card. That keeps `og:image` from contradicting the `og:url` it sits beside, and is what makes the enumeration total -- every card URL the site emits is one the build prerendered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G5zjYc2BoFV2NjKBBapC7C
hotlong
marked this pull request as ready for review
August 26, 2026 00:44
This was referenced Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #185
The generated share card was locale-independent in two compounding places, so every locale served one English card. This passes the locale through and renders each card in the language of the page it belongs to — the adjudicated option, not the strip-the-text one.
What changed
lib/source.ts—getPageImage(page, lang)puts the locale in the card URL. The card is a rendered artifact of the page's text, so a Japanese page and its English source are two different images and need two URLs.langis required, not optional-with-a-default, for the same reasonlanguageAlternatesrequires itslocales: the defect being fixed is a call site that did not pass a language, and a default is exactly what lets the next call site reproduce it silently. Required makestscthe thing that notices — which is this card's answer to the lint question, scoped to one signature rather than generalised (that stays with #184).The locale is spelled out for every locale, English included, rather than hidden the way
localeUrlhides the default language.hideLocaleexists so/docs/xreads as the plain English URL and nothing reads an image asset path; and an omittedenwould make the first segment ambiguous — the handler would have to guess whether/og/docs/es/…is the Spanish card or the English card for a page sluggedes. That guess fails silently the day someone addscontent/docs/es.mdx.app/og/docs/[...slug]/route.tsx— passes the language to the loader (the missing argument), and enumerates static params overtranslatedLocales(). Enumerated from the English page set because English is the authored source:getPages()with no argument returns all seven locales concatenated (553) andgetPages(lang)cannot tell a translation from a fallback, so neither answers "which pages exist".Also sets
dynamicParams = false. Without it the route stays willing to render any locale for any page on demand, so/og/docs/ja/operate/backup/image.pngwould render English text under ajaURL — the exact incoherence being removed, just at an unlinked URL. This is a small deliberate addition beyond the literal defect; flagging it for review rather than burying it. It is safe because both sides derive the locale identically (see below), and verified below to 404 nothing the site actually emits.app/[lang]/docs/[[...slug]]/page.tsx—shareCardUrl(page, lang)resolves throughcontentLang.The card URL on an untranslated locale route
Flagged in dispatch as not pre-decided. The card URL follows the #188 fallback rule: on an untranslated locale route it names the English card.
On
/ja/docs/architecturethe body being served is the English page, so the card depicting it is the English card. Pointing at ajacard would mean generating a second URL that renders byte-identical English pixels, and — worse —og:imagesits beside anog:urlthat names the English page, so a card URL claiming to be Japanese would contradict the object it illustrates. That is the new incoherence dispatch warned about.This also makes the fallback rule and the cost ceiling the same rule rather than two constraints that happen to agree.
contentLangis a member oftranslatedLocales(slugs)by construction (canonicalLocalereturns either a locale already in that list, or the default language, which the list always contains for a page with an English source). So no card URL this site can emit falls outside the enumeration — which is what makesdynamicParams = falsesafe.Verification
Read from the prerendered output of a cold build, not from the source.
Card count —
translatedLocales()sum, not 79 × 7. Independently computed from the content tree (79 English.mdx, plus locale siblings): 335. Prerenderedimage.png.bodyfiles: 335. Before: 79. A naive seven-per-page would be 553. Per locale: en 79, zh-Hans 62, ja/es/fr/ko 39, de 38.A page with no translation still generates exactly one card. 17 pages are English-only.
architectureproduces exactly one card (en/architecture), and theja/de/koroutes all name that English card, withcanonicalagreeing:The card URL differs per locale for a translated page —
/docs/build/interface/views, seven distinct URLs where there was one:The card text is in that locale's language. Reading pixels is not practical, so this is established two ways that together are stronger than either.
The renderer is deterministic, proved not assumed: all 79/79 English cards are byte-identical between the before build (
og/docs/{slug}/image.png.body) and the after build (og/docs/en/{slug}/image.png.body) — 0 differing, 0 missing. Same inputs, same bytes; the English card is also unchanged by this PR.Given determinism, differing bytes prove different text reached the image: all 256/256 non-English cards differ from their English sibling; 0 were byte-identical.
And the text is the right language, not merely different — the route's inputs, the frontmatter the loader resolves per locale for
build/interface/views:(
dekeeps the English loanword "Views" as its title; its card still differs from the English one because the description is translated — consistent, not an exception.)Nothing 404s and nothing contradicts canonical. Across all 553 prerendered page renders: card URLs pointing at something not prerendered = 0;
og:imagelocale disagreeing with the canonical's locale = 0;twitter:imagediffering fromog:image= 0.Build wall-clock
Cold builds (
rm -rf .next), each holding the container's shared heavy-verify lock so neither competed with the sibling agent building concurrently. Two samples per side:d394550)29625b1)1.15x wall-clock for 4.24x the image count (79 → 335). Ceiling was 2x; this is well under it, and matches the prediction that parallel rendering of text-only variation would not cost proportionally.
Gates
pnpm run type-check(@objectos/docs) — exit 0, re-run on final commit29625b1. It reportedTypes generated successfullywith no diagnostics.pnpm run build— exit 0,29625b1, prerendering 335 cards.content/, so the translation-ownership gate has nothing to say here. Nopackages/, no changeset flow in this repo.One thing I did not do
AGENTS.md asks for a real browser before claiming UI work is done. I did not drive a browser, and say so rather than implying otherwise. It would also be the weaker instrument here:
og:imageis never painted in the page's own viewport — it is consumed by crawlers out of the document head — so the prerendered head and the generated card bytes above are the artifact a browser would only indirectly report on.Generated by Claude Code