Skip to content

perf(i18n): every core component renders with its own English slice - #7358

Draft
vjeux wants to merge 40 commits into
meta:mainfrom
vjeux:perf/i18n-slices-sweep
Draft

vjeux wants to merge 40 commits into
meta:mainfrom
vjeux:perf/i18n-slices-sweep

Conversation

@vjeux

@vjeux vjeux commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Primary intent

Every core component renders with its own English slice, so an app bundles the strings of the components it renders rather than the whole catalog. Merge order: #7357 (runtime), #7969 (codemod), then this. No other template fits.

User or maintainer impact

  • A page that doesn't mount InternationalizationProvider loads 6.2 to 6.8 KB less gzip (table below). PowerSearch keeps the whole catalog for its caller-supplied operator keys.
  • With the provider mounted, a page grows 1.2 to 1.9 KB: the whole catalog is assembled from the slices, which compress worse than one module.
  • A value rendered straight into InternationalizationContext no longer drives core components' strings; feat(cli): migrate a raw InternationalizationContext value to the provider #7969's codemod migrates it.
  • Merging conflicts with 49 open pull requests, mostly on their const t = useTranslator() line.

Problem and solution fit

What led to it: Astryx's i18n work, in three pull requests. Every component read the whole English catalog through useTranslator(), so every page carried every string.

Options:

  • Keep one catalog. No churn, but every page keeps the cost.
  • A module per string. The finest split, but every call site imports its keys one by one.
  • A slice per component namespace (chosen): keys are already grouped as @astryx.<ns>.*, so the sweep is mechanical and each component imports one or two slices. The cost is the provider-mounted growth and the conflicts above.

The sweep, per file:

-import {useTranslator} from '../i18n';
+import {useComponentTranslator} from '../i18n/useComponentTranslator';
+import buttonMessages from '../i18n/generated-locales/en/button.generated';
-  const t = useTranslator();
+  const t = useComponentTranslator(buttonMessages);

check:i18n-catalog gains a rule: a component's keys must be in its slices, and packages/core/src may not import useTranslator or InternationalizationContext.

Authority

Owner decision (pending): retiring direct InternationalizationContext configuration is a breaking change; #7969's codemod is the migration; the alternative is to keep the direct path working. Proposed: accept.

The release owner's 0.8.0 plan for the [breaking] entry gates the stack too.

Change and evidence

Gzip, the PR size report's measure, taken against main 41e8b4e:

Page imports No provider Provider mounted
Button 41,086 → 34,306 B 41,345 → 42,660 B
Dialog, DialogHeader 52,309 → 45,598 B 52,584 → 53,793 B
DateInput 91,739 → 85,507 B 92,003 → 93,857 B
List, ListItem 52,059 → 45,383 B 52,331 → 53,606 B

Scope

  • This PR has one primary intent.
  • Independently removable deltas were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

  1. i18n.source-build.test.mjs builds Button and checks it imports its own slice, not the catalog or the context.
  2. useComponentTranslator.test.tsx checks the catalog assembled from the slices equals en.json, so a mounted provider shows every string.
  3. pnpm check:i18n-catalog passes: every component's keys are in its slices.
  4. The core suite, typecheck and the docsite build pass.
  5. Not this change: check:changesets is red on the release gate ([breaking] while main plans patch 0.7.5); a Breadcrumbs focus case that can fail under load is the race test(Breadcrumbs): the hover-focus case hovers a row the menu's first frame did not focus #8383 fixes; a PowerShell cold-start timeout in the CLI's package-manager.test.mjs is fixed by test(cli): PowerShell's cold start no longer times out the first commandInFolder test #8530.

@vercel

vercel Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Error Error Oct 28, 2026 2:23am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 1:43 PM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 11, 2026
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 11, 2026
@vjeux

vjeux commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Test count for the body: pnpm vitest run --maxWorkers=4 on Node 24.11 — 1,110 files / 23,053 tests passed, 0 failed. ESLint 0 errors; check:repo clean; check:i18n-catalog: 130 useComponentTranslator calls carry the namespaces their files read.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Changes requested: the slicing itself looks sound, since strings resolve the same with and without InternationalizationProvider and the catalog check and test suites pass. The one blocker is compatibility: in the latest release a value rendered straight into the public InternationalizationContext drives useLocale, useDirection, useCollator and every core component's strings, and with this change it reaches none of them, so it is a breaking change for those apps rather than an undocumented path. Please either declare it in a [breaking] Changeset that names the old usage and the migration to InternationalizationProvider (main currently plans a minor, so it is admissible) or keep that path working, and drop the wording that calls InternationalizationContext unchanged.

  • Minor: the i18n docs page now says an app bundles only the strings of the components it renders, but apps that mount the provider or call useTranslator() still carry the whole catalog, so please qualify that sentence.

[Automated review]

Comment thread .changeset/i18n-en-namespace-slices.md Outdated
'@astryxdesign/cli': patch
---

[chore] The i18n runtime can hand a component its own English strings. The English catalog is also emitted one namespace at a time, the resolver takes its English fallback per call, and an internal `useComponentTranslator(slice)` lets a core component render with its own slice; the components themselves move to it in a follow-up, so nothing changes for an app yet. `useTranslator()` and `InternationalizationContext` are unchanged and still resolve every `@astryx.*` key; an `InternationalizationProvider`'s `messages` and `overrides` still win; the slices themselves are not importable. One undocumented path moves: a value handed straight to `InternationalizationContext` without the provider no longer reaches `useLocale`, `useDirection` and `useCollator`, which read the provider's runtime context; mount `InternationalizationProvider`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a breaking change to a released public surface rather than an undocumented path: InternationalizationContext is exported from @astryxdesign/core/i18n, and in 0.6.8 a value rendered straight into it drives useLocale, useDirection, useCollator and every core component's strings, while at this head it reaches none of them. Please move it into a [breaking] Changeset (minor) that names the old usage, the new behavior and the migration to InternationalizationProvider, or keep that path working. The “nothing changes for an app yet” sentence also stops being true once both halves ship together.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Declared [breaking] in #7357 (305efa8, the PR that moves useLocale/useDirection/useCollator to the runtime context): the changeset names the old usage — a value rendered straight into InternationalizationContext without the provider — and the migration, mount InternationalizationProvider with the same locale, messages and overrides. Keeping the path alive would put the whole catalog back into every component's bundle, which is what the change removes. The "unchanged" wording is gone from both bodies and both changesets (9f3e2d4 here).


export function useLocale(): Locale {
const ctx = use(InternationalizationContext);
const ctx = use(TranslationRuntimeContext);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reading only TranslationRuntimeContext here (and in useDirection, useCollator and useComponentTranslator) is what stops a value handed straight to the public InternationalizationContext from reaching these hooks. If that released path should keep working, this is where a fallback would live; otherwise the change needs the breaking declaration noted on the changeset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The fallback would have to be the public context's whole-catalog translate, which puts the catalog back into every component that reads it — so the path retires instead, declared [breaking] in #7357 with the migration to the provider.

{
type: 'prose',
text: 'Astryx component authors read strings with `useTranslator()` rather than hardcoding user-facing text.',
text: "Astryx components read their own strings from the English catalog rather than hardcoding them, so an app bundles only the strings of the components it renders. Your own code reads any `@astryx.*` key with `useTranslator()`:",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This holds only for apps that neither mount InternationalizationProvider nor call useTranslator(); both still carry the whole catalog, and the next block teaches useTranslator(). Suggest qualifying the sentence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Qualified in 9f3e2d4: "An app that neither mounts InternationalizationProvider nor calls useTranslator() bundles only the strings of the components it renders; one that does carries the whole catalog, each string once." (The sentence lives in this PR, where it becomes true; #7357's copy of the guide makes no bundle claim.)

@vjeux
vjeux force-pushed the perf/i18n-slices-sweep branch 3 times, most recently from 9f3e2d4 to 59f0ee1 Compare October 12, 2026 00:29
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Preview availability: Storybook and Sandbox are not both ready on the exact-head Vercel preview. CI concluded cancelled.

Modified Components

AlertDialog (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 191B 191B 0B
Lines of Code 182 183 -
Complexity Medium Medium (12) -
AppShell (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 785B 785B 0B
Lines of Code 608 609 -
Complexity Very High Very High (109) -
Banner (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 1.2KB 1.2KB 0B
Lines of Code 487 488 -
Complexity Very High Very High (47) -
Button (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 712B 712B 0B
Lines of Code 584 585 -
Complexity Very High Very High (83) -
Calendar (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 717B 717B 0B
Lines of Code 1641 1643 -
Complexity Very High Very High (171) -
Carousel (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 272B 272B 0B
Lines of Code 486 487 -
Complexity Very High Very High (63) -
CheckboxInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 478B 478B 0B
Lines of Code 355 356 -
Complexity Very High Very High (39) -
Citation (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 340B 340B 0B
Lines of Code 229 230 -
Complexity High High (20) -
CodeBlock (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 911B 911B 0B
Lines of Code 2179 2180 -
Complexity Very High Very High (125) -
ComplexSelector (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 258B 258B 0B
Lines of Code 646 649 -
Complexity Very High Very High (73) -
ContextMenu (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 1.1KB 1.1KB 0B
Lines of Code 678 681 -
Complexity Very High Very High (86) -
DateInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 266B 266B 0B
Lines of Code 3532 3549 -
Complexity Very High Very High (390) -
DateRangeInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 291B 291B 0B
Lines of Code 621 632 -
Complexity Very High Very High (81) -
DateTimeInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 286B 286B 0B
Lines of Code 3609 3633 -
Complexity Very High Very High (559) -
FileInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 465B 465B 0B
Lines of Code 726 729 -
Complexity Very High Very High (108) -
Kbd (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 291B 291B 0B
Lines of Code 147 149 -
Complexity High High (16) -
Lightbox (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 394B 394B 0B
Lines of Code 804 805 -
Complexity Very High Very High (87) -
Markdown (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 616B 616B 0B
Lines of Code 8690 8692 -
Complexity Very High Very High (287) -
MoreMenu (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 219B 219B 0B
Lines of Code 87 88 -
Complexity Low Low (4) -
MultiSelector (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 273B 273B 0B
Lines of Code 2642 2652 -
Complexity Very High Very High (344) -
NumberInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 502B 502B 0B
Lines of Code 1152 1154 -
Complexity Very High Very High (176) -
Outline (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 607B 607B 0B
Lines of Code 1216 1217 -
Complexity High High (28) -
Pagination (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 597B 597B 0B
Lines of Code 645 646 -
Complexity Very High Very High (97) -
Popover (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 412B 412B 0B
Lines of Code 977 979 -
Complexity Very High Very High (145) -
PowerSearch (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 640B 640B 0B
Lines of Code 3948 3972 -
Complexity Very High Very High (348) -
Spinner (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 405B 405B 0B
Lines of Code 296 297 -
Complexity High High (25) -
Switch (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 398B 398B 0B
Lines of Code 569 570 -
Complexity Very High Very High (50) -
TextArea (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 455B 455B 0B
Lines of Code 462 465 -
Complexity Very High Very High (72) -
TextInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 480B 480B 0B
Lines of Code 381 384 -
Complexity Very High Very High (70) -
Thumbnail (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 164B 164B 0B
Lines of Code 355 358 -
Complexity Very High Very High (45) -
TimeInput (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 411B 411B 0B
Lines of Code 1245 1258 -
Complexity Very High Very High (194) -
Timestamp (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 331B 331B 0B
Lines of Code 674 676 -
Complexity Very High Very High (55) -
Toast (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 304B 304B 0B
Lines of Code 1382 1384 -
Complexity Very High Very High (144) -
Token (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 566B 566B 0B
Lines of Code 459 460 -
Complexity High High (26) -
Tokenizer (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 331B 331B 0B
Lines of Code 1923 1934 -
Complexity Very High Very High (308) -
TreeList (@astryxdesign/core)
Metric Before After Delta
Entry File (ESM) 648B 648B 0B
Lines of Code 1068 1069 -
Complexity Very High Very High (137) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
charts/Chart N/A 56.9KB N/A N/A 20B N/A
charts/ChartLegend N/A 46.5KB N/A N/A 20B N/A
core/AlertDialog 57.3KB 50.4KB -6.9KB 12.4KB 12.4KB 0B
core/AppShell 58.3KB 51.4KB -6.8KB 11.6KB 11.6KB 0B
core/Avatar 37.8KB 30.9KB -6.9KB 6.3KB 6.3KB 0B
core/AvatarGroup 40.2KB 33.3KB -6.9KB 6.4KB 6.4KB 0B
core/AvatarGroupOverflow 40.3KB 33.5KB -6.8KB 6.4KB 6.4KB 0B
core/AvatarStatusDot 7.1KB 7.1KB 0B 3.4KB 3.4KB 0B
core/Banner 48.2KB 41.3KB -6.9KB 9.9KB 9.9KB 0B
core/BaseTypeahead 54.7KB 47.9KB -6.8KB 9.9KB 9.9KB 0B
core/BreadcrumbItem 66.8KB 59.9KB -6.9KB 11.0KB 11.0KB 0B
core/Breadcrumbs 22.8KB 15.9KB -6.9KB 3.3KB 3.3KB 0B
core/Button 43.7KB 36.6KB -7.1KB 9.2KB 9.2KB 0B
core/Calendar 55.3KB 48.5KB -6.8KB 10.0KB 10.0KB 0B
core/Carousel 50.6KB 43.8KB -6.8KB 10.2KB 10.2KB 0B
core/ChatComposer 61.0KB 54.4KB -6.7KB 10.8KB 10.8KB 0B
core/ChatComposerDrawer 24.3KB 17.6KB -6.7KB 4.5KB 4.5KB 0B
core/ChatComposerInput 56.2KB 49.5KB -6.7KB 10.1KB 10.1KB 0B
core/ChatComposerTokenElement 56.2KB 49.5KB -6.7KB 10.1KB 10.1KB 0B
core/ChatDictationButton 46.9KB 39.9KB -6.9KB 9.5KB 9.5KB 0B
core/ChatLayout 49.2KB 42.3KB -6.9KB 9.9KB 9.9KB 0B
core/ChatLayoutScrollButton 46.8KB 39.8KB -7.0KB 9.6KB 9.6KB 0B
core/ChatMessage 23.4KB 16.5KB -6.9KB 3.5KB 3.5KB 0B
core/ChatMessageBubble 7.0KB 7.0KB 0B 3.6KB 3.6KB 0B
core/ChatMessageList 29.3KB 22.3KB -7.0KB 7.8KB 7.8KB 0B
core/ChatMessageMetadata 26.2KB 19.5KB -6.7KB 3.9KB 3.9KB 0B
core/ChatSendButton 46.2KB 39.2KB -7.0KB 9.2KB 9.2KB 0B
core/ChatSystemMessage 6.7KB 6.7KB 0B 3.7KB 3.7KB 0B
core/ChatTokenizedText 6.9KB 6.9KB 0B 3.9KB 3.9KB 0B
core/ChatToolCalls 32.3KB 25.3KB -7.0KB 8.9KB 8.9KB 0B
core/CheckboxInput 50.3KB 43.4KB -6.9KB 9.3KB 9.3KB 0B
core/CheckboxList 46.4KB 39.5KB -7.0KB 8.1KB 8.1KB 0B
core/CheckboxListItem 54.5KB 47.6KB -6.9KB 10.5KB 10.5KB 0B
core/Citation 26.3KB 19.6KB -6.8KB 4.1KB 4.1KB 0B
core/CodeBlock 60.9KB 54.0KB -6.9KB 10.7KB 10.7KB 0B
core/CommandPalette 51.4KB 44.7KB -6.6KB 11.8KB 11.8KB 0B
core/CommandPaletteEmpty 6.1KB 6.1KB 0B 3.3KB 3.3KB 0B
core/CommandPaletteFooter 23.6KB 17.1KB -6.6KB 3.5KB 3.5KB 0B
core/CommandPaletteGroup 3.5KB 3.5KB 0B 3.3KB 3.3KB 0B
core/CommandPaletteInput 32.7KB 25.9KB -6.8KB 8.1KB 8.1KB 0B
core/CommandPaletteItem 8.4KB 8.4KB 0B 3.5KB 3.5KB 0B
core/CommandPaletteList 22.8KB 16.1KB -6.7KB 3.3KB 3.3KB 0B
core/ComplexSelector 56.2KB 49.5KB -6.7KB 10.6KB 10.6KB 0B
core/ContextMenu 75.0KB 68.2KB -6.8KB 14.6KB 14.6KB 0B
core/ContextMenuItem 53.3KB 46.4KB -6.9KB 9.6KB 9.6KB 0B
core/DateInput 99.9KB 93.6KB -6.4KB 14.8KB 14.8KB 0B
core/DateRangeInput 69.8KB 63.4KB -6.4KB 11.2KB 11.2KB 0B
core/DateTimeInput 110.1KB 104.1KB -6.0KB 15.2KB 15.2KB 0B
core/Dialog 16.2KB 16.2KB 0B 7.1KB 7.1KB 0B
core/DialogHeader 49.2KB 42.3KB -6.9KB 9.8KB 9.8KB 0B
core/Drawer 31.6KB 31.6KB 0B 8.0KB 8.0KB 0B
core/DrawerHeader 49.3KB 42.4KB -6.9KB 11.2KB 11.2KB 0B
core/DropdownMenu 73.1KB 66.3KB -6.8KB 14.7KB 14.7KB 0B
core/DropdownMenuCheckboxItem 16.1KB 16.1KB 0B 5.6KB 5.6KB 0B
core/DropdownMenuDivider 6.6KB 6.6KB 0B 3.6KB 3.6KB 0B
core/DropdownMenuGroup 6.1KB 6.1KB 0B 3.3KB 3.3KB 0B
core/DropdownMenuItem 15.4KB 15.4KB 0B 5.2KB 5.2KB 0B
core/DropdownMenuRadioGroup 6.0KB 6.0KB 0B 3.2KB 3.2KB 0B
core/DropdownMenuRadioItem 16.2KB 16.2KB 0B 5.6KB 5.6KB 0B
core/DropdownMenuSubMenu 58.5KB 51.6KB -6.9KB 9.5KB 9.5KB 0B
core/Field 44.6KB 37.6KB -7.0KB 8.0KB 8.0KB 0B
core/FieldLabel 40.2KB 33.3KB -6.9KB 6.5KB 6.5KB 0B
core/FileInput 54.5KB 47.9KB -6.6KB 10.3KB 10.3KB 0B
core/IconButton 43.7KB 36.7KB -7.1KB 9.2KB 9.2KB 0B
core/InputGroup 45.2KB 38.2KB -7.0KB 8.0KB 8.0KB 0B
core/Kbd 23.4KB 16.6KB -6.8KB 3.5KB 3.5KB 0B
core/LayerProvider 63.7KB 56.8KB -6.9KB 10.1KB 10.1KB 0B
core/Lightbox 51.6KB 44.7KB -6.9KB 9.8KB 9.8KB 0B
core/Link 43.9KB 36.9KB -7.0KB 7.6KB 7.6KB 0B
core/LinkProvider 3.5KB 3.5KB 0B 20B 20B 0B
core/ListItem 53.9KB 47.0KB -6.9KB 10.5KB 10.5KB 0B
core/Markdown 144.8KB 138.6KB -6.2KB 17.4KB 17.4KB 0B
core/MetadataList 23.5KB 16.6KB -6.9KB 3.6KB 3.6KB 0B
core/MetadataListItem 6.5KB 6.5KB 0B 3.4KB 3.4KB 0B
core/MobileNav 51.4KB 44.5KB -6.9KB 10.1KB 10.1KB 0B
core/MobileNavToggle 46.6KB 39.7KB -6.9KB 9.4KB 9.4KB 0B
core/MoreMenu 73.4KB 66.6KB -6.8KB 14.7KB 14.7KB 0B
core/MultiSelector 73.3KB 66.8KB -6.5KB 14.6KB 14.6KB 0B
core/NumberInput 56.1KB 49.4KB -6.7KB 10.7KB 10.7KB 0B
core/Outline 29.0KB 22.1KB -6.9KB 4.0KB 4.0KB 0B
core/Pagination 85.2KB 78.9KB -6.2KB 15.3KB 15.3KB 0B
core/Popover 50.3KB 43.3KB -6.9KB 12.1KB 12.1KB 0B
core/PowerSearch 164.7KB 166.7KB +2.0KB 19.9KB 19.9KB 0B
core/RadioList 46.0KB 39.0KB -7.0KB 8.0KB 8.0KB 0B
core/RadioListItem 52.6KB 45.7KB -6.9KB 9.7KB 9.7KB 0B
core/ResizeHandle 25.5KB 18.6KB -6.9KB 3.9KB 3.9KB 0B
core/SegmentedControl 39.1KB 32.3KB -6.8KB 6.3KB 6.3KB 0B
core/Selector 76.7KB 70.0KB -6.7KB 15.0KB 15.0KB 0B
core/SelectorOption 14.5KB 14.5KB 0B 5.1KB 5.1KB 0B
core/SideNav 58.4KB 51.6KB -6.8KB 10.6KB 10.6KB 0B
core/SideNavCollapseButton 47.0KB 40.0KB -7.0KB 9.5KB 9.5KB 0B
core/SideNavHeading 55.1KB 48.3KB -6.8KB 10.1KB 10.1KB 0B
core/SideNavItem 54.9KB 48.0KB -6.9KB 10.1KB 10.1KB 0B
core/SideNavSection 6.5KB 6.5KB 0B 3.6KB 3.6KB 0B
core/Slider 48.8KB 41.9KB -6.9KB 8.5KB 8.5KB 0B
core/Spinner 26.7KB 19.7KB -7.0KB 7.5KB 7.5KB 0B
core/Step 32.8KB 26.0KB -6.8KB 5.3KB 5.3KB 0B
core/Stepper 47.8KB 40.8KB -7.0KB 9.6KB 9.6KB 0B
core/Switch 48.8KB 41.9KB -6.9KB 9.3KB 9.3KB 0B
core/Tab 9.7KB 9.7KB 0B 4.1KB 4.1KB 0B
core/TabList 40.5KB 33.7KB -6.8KB 7.2KB 7.2KB 0B
core/TabMenu 52.0KB 45.0KB -7.0KB 10.0KB 10.0KB 0B
core/Table 84.2KB 77.8KB -6.5KB 15.2KB 15.2KB 0B
core/TableBody 3.3KB 3.3KB 0B 3.2KB 3.2KB 0B
core/TableCell 76.0KB 69.3KB -6.8KB 14.9KB 14.9KB 0B
core/TableFooter 3.3KB 3.3KB 0B 3.2KB 3.2KB 0B
core/TableHeader 3.3KB 3.3KB 0B 3.2KB 3.2KB 0B
core/TableHeaderCell 75.8KB 69.0KB -6.8KB 14.9KB 14.9KB 0B
core/TableRow 6.2KB 6.2KB 0B 3.6KB 3.6KB 0B
core/TableSelectionToolbar 52.8KB 46.3KB -6.5KB 11.2KB 11.2KB 0B
core/TextArea 49.8KB 43.1KB -6.7KB 9.4KB 9.4KB 0B
core/TextInput 54.0KB 47.2KB -6.7KB 10.5KB 10.5KB 0B
core/Thumbnail 51.1KB 44.2KB -6.9KB 11.0KB 11.0KB 0B
core/TimeInput 87.2KB 80.8KB -6.4KB 13.8KB 13.8KB 0B
core/Timestamp 27.9KB 21.0KB -6.9KB 9.5KB 9.5KB 0B
core/Toast 60.2KB 53.3KB -7.0KB 9.7KB 9.7KB 0B
core/ToggleButton 44.5KB 37.5KB -7.1KB 9.3KB 9.3KB 0B
core/Token 30.0KB 23.2KB -6.8KB 4.5KB 4.5KB 0B
core/Tokenizer 69.0KB 62.6KB -6.4KB 16.5KB 16.5KB 0B
core/Toolbar 40.1KB 33.3KB -6.8KB 7.7KB 7.7KB 0B
core/TopNav 52.2KB 45.5KB -6.8KB 10.4KB 10.4KB 0B
core/TopNavHeading 54.5KB 47.7KB -6.8KB 9.9KB 9.9KB 0B
core/TopNavItem 9.0KB 9.0KB 0B 3.8KB 3.8KB 0B
core/TopNavMegaMenu 54.6KB 47.7KB -6.9KB 10.8KB 10.8KB 0B
core/TopNavMegaMenuFeaturedCard 6.7KB 6.7KB 0B 3.4KB 3.4KB 0B
core/TopNavMegaMenuItem 9.1KB 9.1KB 0B 4.1KB 4.1KB 0B
core/TopNavMenu 54.0KB 47.1KB -6.9KB 10.1KB 10.1KB 0B
core/TreeList 38.6KB 31.6KB -7.0KB 4.7KB 4.7KB 0B
core/Typeahead 61.9KB 55.2KB -6.7KB 11.3KB 11.3KB 0B
core/TypeaheadItem 6.2KB 6.2KB 0B 3.4KB 3.4KB 0B
lab/ChatEmojiPicker N/A 58.7KB N/A N/A 773B N/A
lab/ChatReactionBar N/A 59.3KB N/A N/A 1.1KB N/A
lab/ChatTypingIndicator N/A 24.9KB N/A N/A 612B N/A
lab/CommentComposer N/A 96.3KB N/A N/A 1.9KB N/A
lab/CommentPin N/A 40.9KB N/A N/A 1.2KB N/A
lab/CommentThread N/A 217.8KB N/A N/A 2.4KB N/A
lab/Comments N/A 222.8KB N/A N/A 2.5KB N/A
lab/DiffViewer N/A 47.2KB N/A N/A 2.1KB N/A
lab/EmojiPicker N/A 65.0KB N/A N/A 722B N/A
lab/ListInput N/A 77.2KB N/A N/A 1.6KB N/A
lab/MobilePowerSearch N/A 149.2KB N/A N/A 1.4KB N/A
lab/MobileTokenizer N/A 94.5KB N/A N/A 809B N/A
lab/Schedule N/A 53.3KB N/A N/A 3.9KB N/A
lab/TourStep N/A 48.8KB N/A N/A 590B N/A
lab/TransferList N/A 68.8KB N/A N/A 1.7KB N/A
lab/TransferListSelector N/A 74.8KB N/A N/A 1.9KB N/A
richtext/RichTextEditor N/A 272.5KB N/A N/A 2.6KB N/A
richtext/RichTextEditorToolbar N/A 214.3KB N/A N/A 454B N/A
richtext/RichTextView N/A 253.0KB N/A N/A 2.3KB N/A

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 34.9KB 34.9KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

57 of 504 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
RichTextEditor Markdown parity: side by side probe dark 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232

and 37 more.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This change is on hold for a maintainer decision: approval needs a maintainer to confirm, or record in the governing spec, that InternationalizationContext stops configuring Astryx in favor of InternationalizationProvider, since no current spec decides that. The slicing itself checks out: strings resolve the same with and without the provider, and a Button bundle drops about 5 KB gzip. Independently, please finish the [breaking] Changeset's migration: say how an app that supplied its own translate moves its strings (the provider has no translate input), map the context's direction to the provider's dir, and add an astryx upgrade codemod or note why one would be vacuous. Please also drop its "bundles do not change yet" clause, which contradicts the sweep's Changeset once both ship in 0.7.0.

[Automated review]

Comment thread .changeset/i18n-en-namespace-slices.md Outdated
'@astryxdesign/core': minor
---

[breaking] An app that sets `InternationalizationContext` directly — rendering `<InternationalizationContext value={…}>` (or its `.Provider`) instead of `InternationalizationProvider` — no longer drives core components' strings or `useLocale`, `useDirection` and `useCollator`. Migration: mount `InternationalizationProvider`; it takes the same `locale`, `messages`, `overrides` and `dir`. The provider now writes an internal runtime context that those hooks and every core component read. Reading the public context is unchanged, so a component that reads it with `useContext` and re-mounts `InternationalizationProvider` with a derived value keeps working: `locale`, `direction`, `messages`, `overrides` and `translate`, which still resolves every `@astryx.*` key against the whole shipped English catalog, with or without a provider; `useTranslator()` is unchanged. Groundwork for components carrying only their own English strings: the English catalog is also emitted one namespace at a time, the resolver takes its English fallback per call, and an internal `useComponentTranslator(slice)` lets a core component render with its own slice; the components themselves move to it in a follow-up, so bundles do not change yet. The slices are not importable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two things in this entry:

  • The migration covers locale, messages and overrides, but the released context value type also requires a translate, and when an app supplies its own, that translator produced every core component's strings in 0.6.8. InternationalizationProvider has no translate input, so please say how such an app moves its strings (into messages or overrides), map the context's direction to the provider's dir, and either stage an astryx upgrade codemod or note why one would be vacuous (AST-017 FR8).
  • "the components themselves move to it in a follow-up, so bundles do not change yet" contradicts .changeset/i18n-components-own-slices.md once both ship in 0.7.0; please drop that clause.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both points are covered in the [breaking] entry at this head (8a08a54). The provider has no translate input, so an app that supplied its own expresses those strings as messages (a catalog per locale) or overrides (sparse, per locale), resolved in the documented order. The provider's dir takes the value's direction.

@vjeux
vjeux force-pushed the perf/i18n-slices-sweep branch from 59f0ee1 to cc58ddd Compare October 12, 2026 01:17
@vjeux

vjeux commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

On the migration asks, done in #7357 (c9fbefe, which this branch is rebased on; head cc58ddd here):

  • An app that supplied its own translate: the provider has no translate input, so those strings become messages (a catalog per locale) or overrides (sparse, per locale), resolved in the documented order. Said in the changeset.
  • direction → the provider's dir. Said in the changeset.
  • Codemod: astryx upgrade now runs migrate-internationalization-context — <InternationalizationContext value={v}> (or .Provider) becomes <InternationalizationProvider locale={v.locale} dir={v.direction} messages={v.messages} overrides={v.overrides}>; an inline object maps key by key; a translate key stays behind a TODO; only JSX bound to an Astryx import is rewritten and a file that only reads the context is left alone. Tests cover each case; it has its own [feat] changeset for the CLI.
  • The "bundles do not change yet" clause is gone from the breaking changeset.

The hold itself — a maintainer confirming that InternationalizationContext stops configuring Astryx in favor of InternationalizationProvider — is the maintainers' to lift; the blast radius in #7357's body (3 external uses, all readers that re-mount the provider, none setting the raw context) is there to inform it.

vjeux added 3 commits October 25, 2026 03:46
The upgrade transform, its fixtures and its changeset leave this PR so the
runtime half stays within the review size; the breaking entry points at
the stacked PR that carries them. CONTRIBUTING's bundle sentence names
PowerSearch, whose caller-supplied operator keys keep the whole catalog.
… will

The core changelog takes this entry verbatim, so it names the `astryx upgrade`
transform and what it rewrites rather than pointing at the pull request that
ships it. CONTRIBUTING's PowerSearch clause moves to the component sweep,
where the hook it names exists.
…stays the package's own

`astryxStylex()` aliased `@astryxdesign/core/locales/(.+).generated.js`
straight to the source tree, which let a Vite app resolve the per-namespace
English slices the exports map keeps out of reach with a `null` target.
The capture now takes a single segment, the whole-catalog case; a slice
specifier under `locales/en/` resolves to nothing the package ships. Test
for the slice specifier beside the existing catalog cases.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for an owner decision: component:InternationalizationProvider lists the provider's context behavior and hooks as stable public API, so retiring direct InternationalizationContext configuration needs an engineering owner to accept that removal as the contract's compatibility decision, or to say that the direct-context path must keep working. An engineering owner records that decision by commenting /approve-spec 8a08a5472142451792ff8a1816681a9e9692b33d on this pull request, and the author may do so as an engineering owner. Independently of that decision:

  • check:changesets fails while main declares 0.7.5, so the release owner needs to declare 0.8.0 on main before the [breaking] entry can merge.
  • Please land #7357 and #7969 first and rebase, so that this diff is only the component sweep.
  • The [perf] Changeset says an app with the provider "moves by under 1 KB either way", but the size table in the description shows every listed page growing 1.2 to 1.9 KB gzip with the provider mounted, so please state the measured growth.

[Automated review]

'@astryxdesign/cli': patch
---

[perf] A component carries only its own English strings. Every core component now renders with its own namespace slice of the English catalog instead of the whole catalog, so an app that renders components without mounting `InternationalizationProvider`, calling `useTranslator()` or importing `InternationalizationContext` (each of which carries the whole catalog), and that does not render `PowerSearch` (whose caller-supplied operator keys keep the whole catalog as their fallback), bundles only the strings of the components it renders: two rows of buttons and links drop about 5 KB (gzip) from their first load. An app that does mount the provider still carries the whole catalog, now assembled from the same slices so no string ships twice; it moves by under 1 KB either way (a few slice modules cost a little, many save a little). `useTranslator()` is unchanged. `astryx swizzle` points an ejected component's strings at the public `useTranslator()`: the internal translator import, the slice imports and a module-level merge of slices are rewritten, so the copy compiles in the app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This says an app that mounts the provider "moves by under 1 KB either way", but the size table in the description shows every listed page growing with the provider mounted (Button 41,345 to 42,660 B, DateInput 92,003 to 93,857 B gzip, about 0.9 KB only when the provider is imported first). Please state the measured growth, or drop the figure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 144793f. The entry now gives the measured sizes. Without the provider, a page importing Button, Dialog, DateInput or List loads 6.2 to 6.8 KB (gzip) less. With it mounted, the page loads 1.2 to 1.9 KB more, because the catalog spread over 83 slice modules compresses worse than one module; the growth is about 0.9 KB when the provider is imported before the components. It also drops the old "about 5 KB" figure from the October 11 base.

'@astryxdesign/core': minor
---

[breaking] An app that sets `InternationalizationContext` directly — rendering `<InternationalizationContext value={…}>` (or its `.Provider`) instead of `InternationalizationProvider` — no longer configures Astryx: it drives neither `useLocale`, `useDirection` and `useCollator`, nor the components that read the locale or direction through them (the date, date-time, date-range and number inputs, Calendar, Chat, PowerSearch, Table sorting and filtering, Timestamp), nor any core component's strings — every component now renders with its own English slice through an internal translator, and `DateTimeInput`'s touch field reads `useLocale()`. One deliberate exception: a `PowerSearch` operator `i18nKey` from outside PowerSearch's own namespace (a key of another Astryx component, or of the app's catalog) still resolves through the public context's `translate`, since a caller may supply any key there; the shipped default operators are PowerSearch's own and read the slice like the rest of the component. Migration: mount `InternationalizationProvider`; its `locale`, `messages` and `overrides` take the value's fields of the same name, and its `dir` takes the value's `direction`. The provider has no `translate` input: an app that supplied its own `translate` expresses those strings as `messages` (a catalog per locale) or `overrides` (sparse, per locale) and the provider resolves them in the documented order. Run `astryx upgrade`: its `migrate-internationalization-context` transform rewrites a variable value to the provider's `locale`, `dir` and `messages` with a TODO for its `overrides` and `translate`, and an inline object literal key by key with a TODO for `translate`; any other value, and any element that spreads props, is left as written behind a TODO saying why. The provider now writes an internal runtime context that those hooks read. Reading the public context is unchanged, so a component that reads it with `useContext` and re-mounts `InternationalizationProvider` with a derived value keeps working: `locale`, `direction`, `messages`, `overrides` and `translate`, which still resolves every `@astryx.*` key against the whole shipped English catalog, with or without a provider; `useTranslator()` is unchanged. The English catalog is also emitted one namespace at a time, the resolver takes its English fallback per call, and an internal `useComponentTranslator(slice)` is what a core component renders with. The slices are not importable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

check:changesets refuses this entry while main declares 0.7.5 (AST-017 FR31). It can merge once an engineering owner accepts the raw-context retirement and the release owner declares 0.8.0 on main (FR48).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, there is nothing to change in the entry itself. It stays refused until an engineering owner rules on the raw-context retirement and the release owner declares 0.8.0 on main. The description's Authority section names both decisions. Checked at 144793f.

vjeux added 7 commits October 25, 2026 06:27
…erence

The docsite's server routes trace this package's files, and the test ran
its resolver from apps/sandbox, a path the tracer follows: the search
route then shipped the whole sandbox app (190.3 MB, over the docsite's
150 MB function budget), so every preview deploy of this branch failed.
The package's self-reference applies the same exports map a consumer's
import does, so the test now resolves from the package's own directory;
the route is back to 59.0 MB (main: 58.9 MB), and the test still fails
when the ./locales/en/* null export is removed.
…vider

`astryx upgrade` runs `migrate-internationalization-context`, the codemod
for the retirement that the stacked runtime change declares: a
`<InternationalizationContext value={v}>` (or `.Provider`) whose value is a
variable maps `locale`, `direction` (as `dir`) and `messages` to
`<InternationalizationProvider>` with a TODO for `overrides` and
`translate`; an inline object literal maps key by key with a TODO for
`translate`; any other value — a call, a conditional, a literal with a
spread, an empty literal — and any element that spreads props stay as
written behind a TODO that says why (a spread after `value` may override
it; a spread anywhere may carry `dir`, `messages` or `overrides`, which the
context ignored but the provider would read). Only an element whose name
resolves, unshadowed, to the Astryx import is touched: blocks, loop heads,
catch clauses, parameters (private methods too), hoisted `var`s, named
class and function expressions, `static {}` blocks, namespace bodies
(including `export`, `import X =`, `declare function`, `enum`, nested and
dotted namespaces) all shadow it; a type-only import proves nothing. The
provider is imported under a free alias when the module already binds its
name, and an emptied import goes with its comments carried forward and
the directive left intact. A second run changes nothing.

Fixtures cover each shape; one typechecks the rewritten output with tsc
against the real i18n source types; one is the read-and-re-mount shape every
real consumer found by a code search has (the transform leaves it alone).
…ir own English slice

Mechanical sweep over every core component that called useTranslator():

  - import {useTranslator} from '../i18n';
  + import {useComponentTranslator} from '../i18n/useComponentTranslator';
  + import buttonMessages from '../i18n/generated-locales/en/button.generated';
  - const t = useTranslator();
  + const t = useComponentTranslator(buttonMessages);

A file that reads several namespaces merges their slices once at module
level (`const messages = {...dateInputMessages, ...timeInputMessages}`) and
passes that. Two PowerSearch files hand `t` to helpers and carry the helpers'
namespace. No call site, key or string changes; check:i18n-catalog verifies
that every slice covers the keys its file reads.
…re files render with their own

With every component on its slice, an app that mounts
InternationalizationProvider or calls useTranslator() carried the whole
catalog through the public context beside the slices, so each rendered
component's strings were in the bundle twice. en.generated.ts is now the
spread of the 78 slice modules, so a bundle that reaches both carries every
string once; a pinned test checks the assembled catalog against the authored
keys and messages, and the compiled Button imports its slice and not the
whole catalog.

check:i18n-catalog gains the slice-coverage rule, here with its first
callers: every @AstryX.<ns>.* key a slice-rendering file reads must belong to
a namespace its slice carries, a file handed t is covered by its component
directory, and an import of useTranslator or InternationalizationContext
inside packages/core/src is refused: both carry the whole catalog, and a
component renders with useComponentTranslator(<its slice>) and reads the
locale through useLocale/useDirection.
…seTranslator()

`astryx swizzle` rewrote `../i18n/useComponentTranslator` and the English
slice imports to `@astryxdesign/core/i18n`, which exports neither, so every
swept component's ejected copy failed to compile (TS2305, TS1192). The copy
step now points the copy at the app's public translator: the hook import
becomes `useTranslator` (a type-only `TranslatorFn` rides along), the slice
imports and a module-level merge of slices go, and each call becomes
`useTranslator()`. Tests: a fixture with a merged slice set, and Button as
swept — nothing its rewritten i18n imports name is internal. Verified by
ejecting Spinner, Button and DateRangeInput into the sandbox app: no i18n
error from tsc.

The internal useCatalogTranslator (caller-supplied keys, PowerSearch) is the
public useTranslator under another name, so its import becomes
`useTranslator as useCatalogTranslator`, keeping both call sites. The barrel
test now covers every core component directory and resolves an `X as Y`
import to X; without the alias it fails on PowerSearch. Ejected PowerSearch
into the sandbox app: four aliased imports, no i18n error from tsc.
…allback

PowerSearch's `operator.i18nKey` is public and unrestricted — any `@astryx.*`
key, or one from the app's own catalog — so resolving it through the
PowerSearch slice lost the shipped fallback a caller had: with no provider
or a partial catalog, `@astryx.table.filter.apply` came back as the raw key
instead of "Apply". A new internal `useCatalogTranslator()` reads the public
context's whole-catalog `translate`; the token, the editor popover, the
search source and the search surface pass it to `resolveOperatorLabel`,
while their own fixed strings keep reading the PowerSearch slice. The
slice-coverage check counts the files that import it so the carve-out stays
visible (4 today, all PowerSearch), and a type-only import clause no longer
registers as a value import. Tests: the token and the whole search → token →
editor path with an out-of-namespace shipped key and no provider; the
compiled hook imports the public context and Button does not import it.

A key in PowerSearch’s own namespace (every shipped default operator) still
resolves through the slice like the rest of the component’s strings, so
under a raw context value the search box and a shipped operator read one
source; only a key from another namespace takes the whole-catalog path.
resolveOperatorLabel takes both translators and routes by namespace.
A page importing Button, Dialog, DateInput or List loads 6.2 to 6.8 KB
(gzip) less without the provider, and 1.2 to 1.9 KB more with it mounted,
because the catalog spread over 83 slice modules compresses worse than one
module (about 0.9 KB when the provider is imported first). The entry said
"under 1 KB either way" and "about 5 KB", from the October 11 base.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for an owner decision: component:InternationalizationProvider lists the provider's context behavior and hooks as stable public API, so retiring direct InternationalizationContext configuration needs an engineering owner to accept that removal and record it in the provider contract as its compatibility decision, or to say that the direct-context path must keep working. An engineering owner records that decision by commenting /approve-spec d576f18e404cba283670572c47c5baf876ff409e on this pull request, and the author may do so as an engineering owner. Independently of that decision:

  • check:changesets fails while main declares 0.7.5, so the release owner needs to declare 0.8.0 on main before the [breaking] entry can merge.
  • Please land #7357 and #7969 first and rebase, so that this diff is only the component sweep.

[Automated review]

vjeux added 4 commits October 27, 2026 03:18
# Conflicts:
#	packages/cli/assets/codemods/transforms/next/index.mjs
# Conflicts:
#	packages/core/src/Markdown/Markdown.tsx
Main's Popover presentation (a bottom sheet on a phone) added a close label
read through useTranslator(), which carries the whole English catalog, so
check:i18n-catalog failed at Popover.tsx after main was merged in. Popover
now renders with useComponentTranslator(popoverMessages), like every other
core component in this sweep.

Red first: check:i18n-catalog (1 slice coverage problem at
Popover/Popover.tsx:67 before, none now).
Main's CommandPaletteEmpty audit (9600039) added a doc that matches
"search" (112 to 113), and this sweep already added one: the merge kept
113 on both sides, so "search search, text and JSON" read 114 against a
golden of 113. Regenerated in the branch, as the test's header says once
the icon-set feature is on main: only the count changes (JSON matchCount
and the text header), and the diff names no experimental surface.

Red first: experimental-isolation's "search search" case (expected 113,
received 114 before; 34 of 34 pass now).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for an owner decision: component:InternationalizationProvider lists the provider's context behavior and hooks as stable public API, so retiring direct InternationalizationContext configuration needs an engineering owner to accept that removal and record it in the provider contract as its compatibility and migration decision, or to say that the direct-context path must keep working. An engineering owner records that decision by commenting /approve-spec fa9550df7468cfb5e1f568c503888f5d960dba71 on this pull request, and the author may do so as an engineering owner. Independently of that decision:

  • check:changesets fails while main declares 0.7.5, so the release owner needs to declare 0.8.0 on main before the [breaking] entry can merge.
  • Please land #7357 and #7969 first and rebase, so that this diff is only the component sweep.

[Automated review]

@vjeux

vjeux commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Owner question: retiring direct InternationalizationContext configuration

Decision: either accept retiring direct InternationalizationContext configuration, recorded in component:InternationalizationProvider's contract as its compatibility and migration decision, or keep the direct-context path working.

Pick: accept the retirement. #7969's codemod migrates a raw InternationalizationContext value to the provider, which gives the contract its migration path.

Blocks: only this pull request, which is already waiting on the release gate (0.8.0).

vjeux added 2 commits October 27, 2026 10:04
# Conflicts:
#	packages/core/src/AlertDialog/AlertDialog.tsx
#	packages/core/src/Tokenizer/Tokenizer.tsx
Main's Tokenizer touch sheet (meta#7977) added TokenizerBottomSheet.tsx, which
read its strings through useTranslator(), the whole English catalog. It
now renders with useComponentTranslator over the three namespaces it reads
(textInput, tokenizer, typeahead), like every core component in this
sweep.

Red first: check:i18n-catalog (a slice coverage problem at
TokenizerBottomSheet.tsx before, none now).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for an owner decision: component:InternationalizationProvider lists the provider's context behavior and hooks as stable public API, so retiring direct InternationalizationContext configuration needs an engineering owner to accept that removal as its compatibility and migration decision (and record it in the provider contract), or to say that the direct-context path must keep working. An engineering owner records that decision by commenting /approve-spec f4f1f69c7158f363eed040aaee5440ce22badb15 on this pull request, and the author may do so as an engineering owner; the owner question posted here proposes the retirement but does not record it. Independently of that decision:

  • check:changesets fails while main declares 0.7.5, so the release owner needs to declare 0.8.0 on main before the [breaking] entry can merge.
  • Please land #7357 and #7969 first and rebase, so that this diff is only the component sweep.

[Automated review]

vjeux added 2 commits October 27, 2026 18:12
# Conflicts:
#	packages/core/src/Chat/ChatPastedTextToken.tsx
#	packages/core/src/FileInput/FileInput.tsx
Main's meta#7849 localized Kbd's key names through useTranslator(), the whole
English catalog. Kbd now renders with useComponentTranslator over the kbd
namespace it reads, like every core component in this sweep.

Red first: check:i18n-catalog (six slice coverage problems in Kbd.tsx
before, none now).

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for an owner decision: component:InternationalizationProvider lists the provider's context behavior and hooks as stable public API, so retiring direct InternationalizationContext configuration needs an engineering owner to accept it as that contract's compatibility and migration decision, or to say that the direct-context path must keep working. An engineering owner records that decision by commenting /approve-spec 9d495a7bda0fe1100523dd6981b1045c22209762 on this pull request, and the author may do so as an engineering owner; the [breaking] entry also needs main to declare 0.8.0, because check:changesets refuses it while main declares 0.7.5. Independently of that decision:

  • Please land #7357 and #7969 first and rebase, so that this diff is the roughly 1,800-line component sweep rather than 3,796 authored lines.
  • .changeset/i18n-components-own-slices.md counts 83 slice modules, but this head emits 84; please drop or correct the count.

[Automated review]

'@astryxdesign/cli': patch
---

[perf] A component carries only its own English strings. Every core component now renders with its own namespace slice of the English catalog instead of the whole catalog, so an app that renders components without mounting `InternationalizationProvider`, calling `useTranslator()` or importing `InternationalizationContext` (each of which carries the whole catalog), and that does not render `PowerSearch` (whose caller-supplied operator keys keep the whole catalog as their fallback), bundles only the strings of the components it renders: a page importing `Button`, `Dialog`, `DateInput` or `List` loads 6.2 to 6.8 KB (gzip) less JavaScript. An app that does mount the provider still carries the whole catalog, now assembled from the same slices so no string ships twice; such a page loads 1.2 to 1.9 KB (gzip) more, because the catalog spread over 83 slice modules compresses worse than one module (about 0.9 KB more when the provider is imported before the components). `useTranslator()` is unchanged. `astryx swizzle` points an ejected component's strings at the public `useTranslator()`: the internal translator import, the slice imports and a module-level merge of slices are rewritten, so the copy compiles in the app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

en.json has 84 namespaces at this head and generate-i18n-runtime.mjs emits 84 English slices, so “spread over 83 slice modules” is stale. Dropping the count, as was done for the key count, keeps the entry from going stale again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f96ddb4: the entry says "its slice modules", with no count.

The changeset said the catalog is spread over 83 slice modules; with main's
kbd namespace this head emits 84, and the count changes with every new
namespace. It now says the catalog is spread over its slice modules.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This pull request is on hold for an owner decision: component:InternationalizationProvider lists the provider's context behavior and hooks as stable public API, so retiring direct InternationalizationContext configuration needs an engineering owner to update that contract for this intentional change, accepting the retirement as its compatibility and migration decision, or to say that the direct-context path must keep working. An engineering owner records that decision by commenting /approve-spec f96ddb4edf87c352db2d0b6d249aea446e2ac6c8 on this pull request, and the author may do so as an engineering owner; the [breaking] entry also needs main to declare 0.8.0, because check:changesets refuses it while main declares 0.7.5. Independently of that decision, please land #7357 and #7969 first and rebase, so that this diff is the roughly 1,800-line component sweep rather than 3,796 authored lines.

[Automated review]

This branch had an error being deployed

1 failed deployment
Preview — f96ddb4e Deployed Oct 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants