Skip to content

WEBDEV-9223: DO NOT MERGE aggregate localization build - #130

Draft
jbuckner wants to merge 68 commits into
mainfrom
WEBDEV-9223-localization-demo
Draft

jbuckner wants to merge 68 commits into
mainfrom
WEBDEV-9223-localization-demo

Conversation

@jbuckner

@jbuckner jbuckner commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

All the elements localization work in one build so offshoot can demo it. This one doesn't merge.

Includes:

On top of those, this branch has Spanish for every string and demo-only localization of the donation form.

Published as 1.1.3-l10n-demo.2 on the alpha tag.

#131 WEBDEV-9242 is left out for now. Its ia-dropdown clashes with the standalone @internetarchive/ia-dropdown that offshoot and collection-browser still load, since both register <ia-dropdown>.

Demo: https://www-offshoot-webdev-9223-localization-demo.dev.archive.org (DEV tab, then ES)

🤖 Generated with Claude Code

https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX

jbuckner and others added 30 commits July 28, 2026 17:06
Moves @internetarchive/ia-dropdown v2.2.0 into src/elements/ia-dropdown/,
so ia-dropdown-search-bar can use the local component and elements can
drop the external dep.

Renames to match the conventions here: IaDropdown -> IADropdown,
IaIconLabel -> IAIconLabel, and the lowercase optionInterface ->
OptionInterface. selectedHandler was typed as bare Function, now it's
(option: OptionInterface) => void, which is what it always was at
runtime. Tests ported from web-test-runner + @open-wc/testing + sinon
to vitest; the sinon sandbox was dead scaffolding so it's gone.

The carets stay as inline Lit svg templates rather than becoming .svg
file imports like other elements icons. They're recolored through
--dropdownCaretColor, and you can't style the inside of an <img>, so
converting them would quietly break that var and the caret-up/caret-down
override slots.

186 tests pass, including the 14 search-bar tests now running against
the local dropdown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnQGMFUXNYTcTL2mvkJ3Dp
The carets were Lit svg templates in .ts files. They're now plain
caret-up.svg / caret-down.svg like the other icons in the repo.

They're imported with ?raw and inlined with unsafeHTML rather than
rendered as <img src>, because the caret color is driven by the
--dropdownCaretColor CSS var and that can't reach inside an <img>.
Four places rely on it: this repo's own ia-dropdown-search-bar,
offshoot's loan-filter-bar, and collection-browser's sort-filter-bar
and smart-facet-dropdown.

Verified in the browser that the carets are still real inline <svg>
elements with their caret-up-svg / caret-down-svg classes, that the
var still drives the fill, and that the search bar's caret resolves
to its dark #2c2c2c rather than the default white.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnQGMFUXNYTcTL2mvkJ3Dp
Ports ia-topnav out of the iaux monorepo as ia-topnav, with every
sub-element namespaced ia-topnav-* so it can't collide with the copies
petabox and offshoot still load. element-names.test.ts holds that line:
it checks the new names register and that all 17 generic names the old
package claims stay free, since both packages load during the migration
and a duplicate define throws rather than degrading.

Ported from the WEBDEV-9036 branch rather than iaux master, so the
uploader and biblio admin sections are here from the start. That PR
touches most of what this move carries, so porting master would have
meant doing the same work twice.

No dependencies added, two dropped. ia-wayback-search only supplied a
base class, and importing it registered the generic ia-wayback-search
tag as a side effect, so it's one flattened element now. ia-styles only
supplied the sr-only block, which is now a shared style in themes.

Per-component style modules became inline styles, which is what every
other element here does. The shared subnav fragment keeps a plain name
at the component root. The user menu and signed-out dropdown returned
exactly the base class's styles, and Lit already inherits those, so the
overrides are gone.

Three things dropped as dead: the ia-icon element, which nothing
imported or rendered and which squatted a generic name; a write-only
state field on the login button; and the redundant style overrides
above.

Two bugs came out of the move. Two tests had every assertion commented
out, so they passed while checking nothing; both are rewritten against
real behavior, taking the component from 52 tests to 94. And the
wayback search's desktop icon read a variable nothing sets, with no
fallback, so its fill resolved to invalid and painted black against the
dark nav. It follows the topnav's own icon color now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpyQxVSkZFrWxxGamCcNhY
Four assertions used `to.not.be.undefined` on a querySelector result.
querySelector returns null, so they passed whether or not the element
was found. Switching them to `to.exist` failed two tests that had never
actually run:

The wayback test looked for the search inside the web subnav, but the
subnav renders the slider and the search sits one level inside that.
The selector never matched.

The login button test clicked the toggle and expected an active class.
The button owns no open state; it reports the click and the parent sets
openMenu. Standalone it can never go active. It now checks both halves
of that contract, the event on click and the class once openMenu is set.

The dropdown-menu test also registered the base class as `dropdown-menu`
at module scope, a name element-names.test.ts asserts stays free. Both
only passed because vitest isolates each file in its own page, so
turning isolation off would have failed one of them depending on load
order. The base class is registered nowhere in the source, so the test
now mounts it under a tag inside the topnav's own namespace.

Also drops a comment that explained the icon fill by contrast with its
old package, and moves the wayback search to the getter form of styles
that every other element here uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpyQxVSkZFrWxxGamCcNhY
The topnav's :host declares every other color knob, but this one had
gone missing, so the nested wayback search read an undefined variable
and the desktop glyph painted black. The package README still documents
it as var(--grey20), which is what it goes back to. The search keeps a
fallback to the topnav's general icon color for the case where it is
mounted on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpyQxVSkZFrWxxGamCcNhY
The fallback only fires when the search is mounted outside a topnav,
and in that case none of the topnav's greys are declared either, so a
var() fallback is as unresolved as the value it was covering for. fill
inherits, so that paints black rather than falling through. A literal
is the only fallback that actually does anything there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpyQxVSkZFrWxxGamCcNhY
…opnav

* origin/main:
  v0.3.0 (#96)
  WEBDEV-8969: Migrate radio-player into elements (#86)
  WEBDEV-8974: Migrate the radio player search handler into elements (#85)
  WEBDEV-8968: Migrate transcript-view into elements (#84)
  WEBDEV-8967: Migrate expandable-search-bar into elements (#83)
  WEBDEV-8966: Migrate scrubber-bar into elements (#82)
  WEBDEV-8965: Migrate playback-controls into elements (#80)
  WEBDEV-8964: Migrate waveform-progress into elements (#78)
  WEBDEV-8963: Migrate audio-element into elements (#77)

# Conflicts:
#	src/elements/index.ts
Vitest writes these under .vitest-attachments when a test attaches an
image, so they are run output, not source. main already ignores the
directory, but .gitignore only applies to untracked files and these had
already been committed here, so the rule never caught them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaEr5YT1xpjK5ydNZPvjwx
… form section into elements

Bottom rung of the donation form stack. The data-models, currency-validator
and form-section packages from iaux-donation-form land in
src/elements/ia-donation-form/ as models/, currency-validator.ts and
form-elements/ia-donation-section.ts.

The 14 model files fold into six. The enums become as-const objects plus a
type, since erasableSyntaxOnly rules out enum. The classes stay classes:
offshoot, the monthly giving portal and the petabox banner all construct
them. The section is ia-donation-section, sized off a 10px base font size
variable like the item navigator so it renders the same under any root font
size, with its CSS variables renamed to --ia-donation-section-*.

story-template gets an importPath property so an element nested inside
another component's directory shows the right import snippet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnX6VHo3EJRzcKfhzBChCf
The edit-donation package from iaux-donation-form lands as
ia-donation-edit-donation under src/elements/ia-donation-form/form-elements/,
rendering ia-donation-section for its two steps. The petabox banner uses it
on its own, so it gets its own story.

Enums become as-const objects plus a type. CSS variables move to
--ia-donation-edit-*, sized off a 10px base font size that is passed through
to the sections. The strings go through msg(), the custom amount field gets
an accessible name, and currency.js becomes a dependency.

One inherited bug fixed: with the custom amount hidden, the "is the donor
typing in the custom field" check compared null to null and came out true,
so a preset set programmatically could end up with nothing selected. Guarded
on the field existing, with a test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnX6VHo3EJRzcKfhzBChCf
The Braintree manager, payment clients and the five provider handlers
(credit card, PayPal, Venmo, Apple Pay, Google Pay) land under
src/elements/ia-donation-form/braintree/, with the recaptcha manager, the
Venmo session restoration handler and DonationFormError alongside. No
custom elements in this rung. The flow handlers go with the modals next.

The vendored 2020 braintree-web and paypal-checkout-components d.ts files
are replaced by the real @types packages, which split the library module
(typeof braintree.client) from the instance (braintree.Client), so the
clients and handlers are typed that way. The fee_amount_covered custom
field is calculated from the fields like the total already was, since
donationInfo can arrive as a plain object. The hosted fields retry no
longer uses an async promise executor and clears its timer whichever way
the race goes. The recaptcha onload callback global is namespaced so it
can't collide with the old package's on a page that loads both.

Tests move from web-test-runner and sinon to Vitest, with the payment
library mocks under test-helpers/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnX6VHo3EJRzcKfhzBChCf
…lements

The rest of donation-form lands under src/elements/ia-donation-form/: the
form, controller, header, summary, total amount, badged input, payment
selector and contact form as ia-donation-* elements, the three modal
contents under modals/, and the payment flow handlers with the donation
flow modal manager under flow-handlers/. The icon-* packages become SVGs,
masked for the monochrome ones and plain <img> for the brand logos. The
controller gets a story wired to the Braintree sandbox with a demo
endpoint manager and buttons for the modals. The story renders in the
light DOM, since Braintree and PayPal find their containers with
document.querySelector, and only loads the third-party scripts once you
press "Connect to the sandbox", so the demo page and its test stay off the
network.

Beyond the move: the payment selector's PayPal cover is a real button
with an aria-label like the other providers, the brand logos have alt
text, and the collapse rule only hides the provider buttons so the cover
survives being pressed with an invalid amount. The upsell thank-you logs
and calls donationSuccessful once instead of twice. The credit card
container lost its duplicate class attribute so the hidden class applies.
The controller no longer replaces a consumer-supplied paymentClients when
environment is set in a later update. The form defaults donationInfo to
the standard selection so the header renders without one. The PayPal
button render reads the funding constants off window.paypal defensively.
TYD is TWD, the confirm modal's currencyType defaults to USD, the hosted
field placeholders go through msg(), and a Google Pay billing name is
trimmed before the first/last split. The form's theming variables are
--ia-donation-form-* and the base font size flows down to the section and
edit elements from the form's host.

modal-manager and analytics-manager are npm dependencies. Tests move to
Vitest, with a mock modal manager element and mock flow handlers under
test-helpers/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnX6VHo3EJRzcKfhzBChCf
The modal-manager element paints its backdrop whenever it's in the DOM, so
the demo page was covered by a black layer that swallowed every click on
every story. The host is expected to hide it, as its README says.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnX6VHo3EJRzcKfhzBChCf
The demo derives the sidebar entry and the anchor from the story's file
name, so ia-donation-form-story.ts showed up as <ia-donation-form> while
the card inside said <ia-donation-form-controller>.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnX6VHo3EJRzcKfhzBChCf
Brings @internetarchive/reviews 1.0.8 across as ia-reviews, ia-review and
ia-review-form, with a demo story and the utils and icons they need.

Adapted to this repo: tests moved to vitest and co-located, customElement
from @src/util/custom-element like the other 28 components, classes on the
IA prefix, and fetch-handler in place of the deprecated
fetch-handler-service. The a11y assertions run on axe-core directly, since
vitest-axe is node-only and the suite runs in real Chromium.

Fixes carried in the move:

- A review body could inject attributes onto the rendered link. The URL
  pattern allowed a quote, the match is interpolated into an anchor and
  rendered with unsafeHTML, and DOMPurify runs before that, so
  `archive.org/x"onmouseover="alert(1)` became a live handler. Quotes and
  angle brackets now end a match, and the href is escaped.
- Delete reported success on any response, because fetch resolves on a 500.
- Delete parameters went unencoded into a query string, so a screenname
  with a space or an ampersand corrupted the request.
- sanitizeReviewBody registered a DOMPurify hook per call on the shared
  singleton, stacking up one per review and reaching other callers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
Matches the newer components here, which keep glyphs as standalone assets
rather than inline lit templates. The class each svg carried moves onto the
img that renders it, so the existing CSS and test selectors still find them.

Drops add-icon and reviews-icon, which nothing imported, and fixes
star-basic's fill, which was "2C2C2C" with no leading hash and so rendered
black rather than grey.

An svg with no intrinsic size renders 300x150 inside an img, so the two
sizes that were previously implicit are pinned by tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
The component only offers its own "write a review" link when an item has
no reviews yet, so with the demo's three reviews the form was unreachable.
A host page supplies its own button and sets displayReviewForm on the
element; the story now does the same and the usage notes say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
ia-review truncates a body past 150 characters and offers More/Less, but
every body in the story sat under that, so the demo never showed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
The demo submitted new reviews as "Ada Fielding", who already has one in
the list, so a review you just wrote looked like it had been attributed to
someone else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
Both whitespace patterns were character classes with a pipe inside, so a
pipe in a review body counted as whitespace: "Rated 8|10 overall" came out
as "Rated 8 10 overall". The classes now list only real whitespace, and
the newline pattern groups \r\n instead of spreading it across a class.

Reported by GitHub code quality on #110, which flagged the duplicate \n in
the second class. The stray pipe was the part that showed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
The submit button's spinner came from @internetarchive/ia-activity-indicator.
ia-status-indicator covers it in loading mode, so the form uses that and the
external dependency goes. hideDots gives the bare ring the button wants, and
the colour and size come through the theme variables rather than the old
component's own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
The four buttons were native elements wearing ia-styles' .ia-button
classes. They're ia-button now: mode="link" for the two in the messages,
secondary for Cancel, primary for Submit.

ia-button renders its own loading state, so the submit spinner and the
ia-status-indicator wiring behind it go away, and @internetarchive/ia-styles
is no longer a dependency. That was the last package here built against
lit 2, so the CSSResult casts go with it.

The link buttons sit inside a sentence, so they reach through
::part(button) to undo the flex display that would otherwise break the
line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyEq51DigmsmwGsa7fEqWS
…opnav

* origin/main:
  1.1.1 (#118)
  WEBDEV-9132: Fix Safari image-viewer wide-layout breakpoint (#115)
  1.1.0 (#116)
  WEBDEV-9129: Exclude story files from coverage reporting (#113)
  WEBDEV-9121: Migrate histogram-date-range into elements (#112)
  WEBDEV-9130: Exclude nested dist and worktree tests from local runs (#114)
  v1.0.0 (#111)
  WEBDEV-9025: Add a CLAUDE.md covering the publish flow (#94)
  WEBDEV-9063: Typecheck in CI (#107)
  v0.4.1 (#108)
  WEBDEV-9066: Guard custom element registration against duplicate defines (#106)
  v0.4.0 (#99)
  WEBDEV-9022: Migrate the image viewer into elements (#93)
…eviews

* origin/main:
  1.1.1 (#118)
  WEBDEV-9132: Fix Safari image-viewer wide-layout breakpoint (#115)
  1.1.0 (#116)
  WEBDEV-9129: Exclude story files from coverage reporting (#113)
  WEBDEV-9121: Migrate histogram-date-range into elements (#112)
  WEBDEV-9130: Exclude nested dist and worktree tests from local runs (#114)
  v1.0.0 (#111)

# Conflicts:
#	package.json
#	pnpm-lock.yaml
…rvice

The form and the review no longer build URLs or call fetch themselves. Both hand a
typed object to a ReviewService and render what comes back. The CSRF token goes out
as an X-CSRF-Token header from the fetch handler, so it's out of the hidden form
field and the delete query string, and delete now sends credentials.

Ported from internetarchive/iaux-reviews#59.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pmQ4SrFjLJ9gdGh4SZCkz
…eviews

* origin/main:
  WEBDEV-9160: Update to pnpm 12 (#119)
  1.1.2 (#121)
  WEBDEV-9192: Make the radio player's waveform music zones opt-in (#120)
jbuckner and others added 2 commits September 30, 2026 16:01
Adds @Localized() to the topnav and donation form elements, turns the contact
form's validation messages into getters, wraps the translatable menu labels in
msg() with a stable English key for analytics, icons and callouts, rebuilds the
menus on a locale change, and fills in Spanish for the new strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjwDQaR3hvZ8J2PP4117W2
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

🚀 View preview at
https://internetarchive.github.io/elements/pr/pr-130/

Built to branch ghpages at 2026-10-02 02:10 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@codecov-commenter

codecov-commenter commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.28427% with 411 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.26%. Comparing base (95784c8) to head (6cd35e8).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/elements/ia-donation-form/ia-donation-form.ts 57.61% 49 Missing and 15 partials ⚠️
...form/flow-handlers/handlers/paypal-flow-handler.ts 11.29% 53 Missing and 2 partials ⚠️
...ts/ia-donation-form/ia-donation-form-controller.ts 78.01% 23 Missing and 8 partials ⚠️
...flow-handlers/handlers/credit-card-flow-handler.ts 43.47% 25 Missing and 1 partial ⚠️
...rm/test-helpers/mock-flow-handlers.test-helpers.ts 43.18% 25 Missing ⚠️
...m/flow-handlers/handlers/apple-pay-flow-handler.ts 0.00% 24 Missing ⚠️
...-form/flow-handlers/handlers/venmo-flow-handler.ts 20.68% 22 Missing and 1 partial ⚠️
src/elements/ia-reviews/ia-review-form.ts 83.68% 16 Missing and 7 partials ⚠️
.../flow-handlers/handlers/google-pay-flow-handler.ts 15.38% 22 Missing ⚠️
...-donation-form/modals/ia-donation-confirm-modal.ts 46.15% 20 Missing and 1 partial ⚠️
... and 23 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #130      +/-   ##
==========================================
- Coverage   91.23%   86.26%   -4.97%     
==========================================
  Files          60      152      +92     
  Lines        2520     5293    +2773     
  Branches      583     1144     +561     
==========================================
+ Hits         2299     4566    +2267     
- Misses         77      470     +393     
- Partials      144      257     +113     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

jbuckner and others added 5 commits October 1, 2026 12:02
The top-level media buttons (Texts, Video, Audio, Software, Images, Donate,
More) came from a module-scope array, so their labels would resolve once at
import. They are built by a function at render now, and wrapped in msg().
Wayback Machine stays as is, it's a product name.

None of the topnav components had @Localized(), so a setLocale() after first
render left them in the old language. Every topnav component that calls msg()
has it now, including the dropdown menu base class.

Two more strings:

- The Wayback search's "1 trillion" page count was a default in the
  module-scope config. It is msg('1 trillion') in the search's render now,
  used when the host passes no count. The search's own stale '916 billion'
  default is gone, so it shows the same figure in both places.
- The dropdown's "New feature: <title>" callout aria-label was never wrapped.

A test with a test-only pseudo-locale switches the locale after first render
and checks the media buttons, a component string, the callout label and the
page count. It fails if any of the fixes is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
The screen reader label on the main button and caret is msg('Toggle options'),
the same message ia-combo-box uses, so the two share a translation. A host that
names its options gets msg(str`Toggle ${optionGroup}`). optionGroup has no
default now, so the label resolves at render rather than when the element is
created. The element has @Localized() so it follows a live locale switch.

This branch has no XLIFF yet. The Spanish it needs:
- Toggle options: Mostrar u ocultar opciones (already in elements' es.xlf)
- Toggle ${optionGroup}: Mostrar u ocultar ${optionGroup}

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
The labels people actually click come from data/menus.ts and none of them
were translated.

78 labels there go through msg() now (73 link titles, 5 headings). Collection
and product names (Live Music Archive, NASA Images, Chrome, Open Library and
so on) and the admin tools block stay as they are.

Each translated link carries a stable English `key`. Analytics event names,
the desktop subnav's CSS classes and Donate icon, and config.callouts (the
"My lists" NEW callout) read `key ?? title`, so they keep working in any
locale.

buildTopNavMenus() runs outside render, so @Localized() alone wouldn't pick up
a new locale: it requests an update with no changed properties and
willUpdate's checks never fire. ia-topnav listens for LOCALE_STATUS_EVENT and
rebuilds the menus on 'ready'.

Tests switch to a test-only pseudo-locale and cover the rebuild, the keys,
the Donate icon, the callout and the analytics names. Each one fails if its
fix is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
Switching to Spanish left most of ia-reviews in English. Ten msg() calls
failed extraction outright (template literals with an expression and no
str tag, and msg() on a stored string), "Delete this review" and
"(edited)" were never wrapped, and none of the three elements re-rendered
on a locale switch.

- Add @Localized() to ia-reviews, ia-review and ia-review-form.
- Use str for the review count, star titles, rating labels, character
  limits and the edited date. Split "Display review(s)" and "Rate N
  star(s)" into whole messages per form.
- Resolve the recaptcha and generic errors, the delete outcome and the
  "Anonymous" fallback when they render, not when the element is built
  or the event fires. submitterScreenname is optional now.
- The review date stays en-us. @lit/localize only exposes getLocale from
  configureLocalization, which the app owns.

ia-reviews-localization.test.ts switches the locale live against a
test-only catalog. Reverting each fix fails its own test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
@jbuckner jbuckner changed the title WEBDEV-9223: DO NOT MERGE combined localization demo WEBDEV-9223: DO NOT MERGE aggregate localization build Oct 1, 2026
jbuckner and others added 12 commits October 1, 2026 12:12
Offshoot's home page renders the Wayback block with the standalone
@internetarchive/ia-wayback-search, which is English only. The topnav's copy
of the same form already lives here with its strings in msg(), so this pulls
it out as its own element, ia-wayback-search-form, and the topnav's
ia-topnav-wayback-search extends it with just its layout overrides. The form
has @Localized(), so both follow a live locale switch.

The tag is ia-wayback-search-form because the legacy ia-topnav that petabox
and offshoot still load registers ia-wayback-search without a guard.

This branch has no XLIFF yet. The Spanish it needs, reused from elements'
es.xlf where it's already there:
- Search the history of more than ${n} web pages on the Internet.: Busca en
  la historia de más de ${n} páginas web de Internet.
- Visit the Wayback Machine: Visita la Wayback Machine
- Search the Wayback Machine: Buscar en la Wayback Machine
- Enter URL or keywords: Escribe una URL o palabras clave
- 1 trillion: 1 billón

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
…s' into WEBDEV-9223-localization-demo

* origin/WEBDEV-9221-localize-topnav-menus:
  WEBDEV-9221: Localize the topnav menu labels
  WEBDEV-9219: Make the topnav follow a live locale switch
…to WEBDEV-9223-localization-demo

* origin/WEBDEV-9240-localize-reviews:
  1.1.3-webdev-9240.0
  WEBDEV-9240: Localize the ia-reviews strings
  1.1.3-webdev-8840.1
  1.1.3-webdev-9107.0
  1.1.3-webdev-8840.0
  WEBDEV-8840: Put the ia-reviews network calls behind an injectable service
  0.4.2-webdev-9107.0
  WEBDEV-9107: Use ia-button for the reviews buttons
  WEBDEV-9107: Use this package's status indicator for the submit spinner
  WEBDEV-9107: Stop collapseSpace eating pipes
  WEBDEV-9107: Name the reviews demo submitter something of its own
  WEBDEV-9107: Give the reviews demo a body long enough to truncate
  WEBDEV-9107: Give the reviews demo a way into the review form
  WEBDEV-9107: Hold the review icons as .svg files
  WEBDEV-9107: Migrate reviews into elements as ia-reviews

# Conflicts:
#	package.json
#	pnpm-lock.yaml
… WEBDEV-9223-localization-demo

* origin/WEBDEV-9241-wayback-search:
  1.1.3-webdev-9241.0
  WEBDEV-9241: Add ia-wayback-search-form for the home page Wayback block

# Conflicts:
#	package.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
Its ia-dropdown registers <ia-dropdown> alongside the standalone
@internetarchive/ia-dropdown that offshoot and collection-browser still
load, and whichever defines second throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6GtWdx3xKytP8c8CLtpAX
Comment thread package.json
{
"name": "@internetarchive/elements",
"version": "1.1.2",
"version": "1.1.3-l10n-demo.2",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DO NOT MERGE. Aggregate demo build that pins prereleases and repeats the commits from the PRs listed in the description. Merge those instead.

jbuckner and others added 7 commits October 1, 2026 16:01
The "quick EN click after ES wins" test awaited Promise.all on two real
setLocale calls, but @lit/localize already serializes and drops stale
loads internally, and both calls were awaiting one shared Deferred — so
their continuations ran in call order regardless of _setLocale's own
request-id guard. The test passed even with that guard removed.

Stubs setLocale per-test so the ES and EN calls get independently
resolvable promises, letting the test deliver the EN reply before the
ES one despite ES being requested first. That's the only way the
request-id guard's check is actually exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lit/localize's own setLocale already serializes concurrent switches
and drops a stale load, so every caller's continuation runs in call
order regardless of which locale's module arrives first. The counter
added in the prior commit duplicated that guarantee; the stale-switch
bug it was chasing was actually the earlier "if (locale ===
this._locale) return" early exit, which skipped a newer request
whenever it matched the not-yet-updated this._locale. Removing that
early exit (now covered by a plain unconditional set after await) is
the fix; the existing "quick EN click after ES wins" test already
catches a regression of it.

Also:
- Warn instead of silently swallowing a rejected _setLocale call at
  both fire-and-forget call sites (the deep-link check and the click
  handler).
- Close an eslint guard gap: `@lit/localize/init/*` re-exports
  configureLocalization and configureTransformLocalization under
  paths the existing `@lit/localize` specifier check didn't cover, and
  configureTransformLocalization wasn't restricted at all.
- Share the customElement restricted-imports list between the two
  no-restricted-imports blocks instead of repeating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* origin/main:
  WEBDEV-9230: Make the demo page usable for QA on a phone (#127)
…witch

Reconciles the demo language switch with WEBDEV-9230's mobile bottom bar
and sheet picker plus the desktop sidebar's Hide nav toggle: the switch
sits in the content header next to the title on desktop, and in the
picker sheet header on a narrow viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* origin/WEBDEV-9249-demo-language-switch:
  WEBDEV-9249: Drop the redundant locale request-id counter
  WEBDEV-9249: Make the race-guard test actually exercise the race
  WEBDEV-9249: Fix a stale locale switch overwriting a quicker one
  WEBDEV-9249: Add a language switch to the elements demo
  WEBDEV-9230: Make the demo page usable for QA on a phone (#127)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants