@astryxdesign/core 0.5.1 → 0.5.2-canary.0a6a1eb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +4 -3
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  6. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  8. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  9. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  10. package/dist/Button/Button.d.ts +2 -1
  11. package/dist/Button/Button.d.ts.map +1 -1
  12. package/dist/Button/Button.js +11 -4
  13. package/dist/Carousel/Carousel.d.ts.map +1 -1
  14. package/dist/Carousel/Carousel.js +2 -2
  15. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  16. package/dist/Chat/ChatToolCalls.js +34 -17
  17. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  18. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  19. package/dist/ContextMenu/ContextMenu.js +149 -26
  20. package/dist/ContextMenu/index.d.ts +1 -0
  21. package/dist/ContextMenu/index.d.ts.map +1 -1
  22. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  23. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  24. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  26. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  28. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  31. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  34. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  37. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  38. package/dist/DropdownMenu/index.d.ts +2 -1
  39. package/dist/DropdownMenu/index.d.ts.map +1 -1
  40. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  41. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  42. package/dist/DropdownMenu/menuWidth.js +35 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  44. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  45. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  46. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  47. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  48. package/dist/MoreMenu/MoreMenu.js +2 -0
  49. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  50. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  51. package/dist/MultiSelector/MultiSelector.js +97 -68
  52. package/dist/MultiSelector/index.d.ts +1 -1
  53. package/dist/MultiSelector/index.d.ts.map +1 -1
  54. package/dist/Popover/Popover.d.ts +10 -6
  55. package/dist/Popover/Popover.d.ts.map +1 -1
  56. package/dist/Popover/Popover.js +141 -16
  57. package/dist/Popover/usePopover.d.ts +4 -0
  58. package/dist/Popover/usePopover.d.ts.map +1 -1
  59. package/dist/Popover/usePopover.js +52 -5
  60. package/dist/Selector/Selector.d.ts +10 -0
  61. package/dist/Selector/Selector.d.ts.map +1 -1
  62. package/dist/Selector/Selector.js +106 -80
  63. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  64. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  65. package/dist/Selector/SelectorBottomSheet.js +83 -0
  66. package/dist/Selector/index.d.ts +1 -1
  67. package/dist/Selector/index.d.ts.map +1 -1
  68. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  69. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  70. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  72. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  73. package/dist/Selector/useSelectorPresentation.js +90 -0
  74. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  75. package/dist/Toast/useToastGesture.js +46 -9
  76. package/dist/astryx.css +19 -1
  77. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  78. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  79. package/dist/hooks/useAdaptivePresentation.js +19 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  81. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  82. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  83. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  84. package/dist/hooks/useFocusTrap.js +12 -0
  85. package/dist/i18n/InternationalizationContext.d.ts +2 -0
  86. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  87. package/dist/i18n/InternationalizationContext.js +3 -1
  88. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  89. package/dist/i18n/InternationalizationProvider.js +3 -1
  90. package/dist/i18n/resolve.d.ts +1 -1
  91. package/dist/i18n/resolve.d.ts.map +1 -1
  92. package/dist/i18n/resolve.js +37 -38
  93. package/dist/i18n/useTranslator.d.ts.map +1 -1
  94. package/dist/i18n/useTranslator.js +2 -3
  95. package/locales/af-ZA.json +0 -4
  96. package/locales/ar-SA.json +0 -4
  97. package/locales/ca-ES.json +0 -4
  98. package/locales/cs-CZ.json +0 -4
  99. package/locales/da-DK.json +0 -4
  100. package/locales/de-DE.json +0 -4
  101. package/locales/el-GR.json +0 -4
  102. package/locales/en.json +20 -0
  103. package/locales/es-ES.json +0 -4
  104. package/locales/fi-FI.json +0 -4
  105. package/locales/fr-FR.json +0 -4
  106. package/locales/he-IL.json +0 -4
  107. package/locales/hu-HU.json +0 -4
  108. package/locales/it-IT.json +0 -4
  109. package/locales/ja-JP.json +0 -4
  110. package/locales/ko-KR.json +0 -4
  111. package/locales/nl-NL.json +0 -4
  112. package/locales/no-NO.json +0 -4
  113. package/locales/pl-PL.json +0 -4
  114. package/locales/pseudo.json +15 -0
  115. package/locales/pt-BR.json +0 -4
  116. package/locales/pt-PT.json +0 -4
  117. package/locales/ro-RO.json +0 -4
  118. package/locales/ru-RU.json +0 -4
  119. package/locales/sr-SP.json +0 -4
  120. package/locales/sv-SE.json +0 -4
  121. package/locales/tr-TR.json +0 -4
  122. package/locales/uk-UA.json +0 -4
  123. package/locales/vi-VN.json +0 -4
  124. package/locales/zh-CN.json +0 -4
  125. package/locales/zh-TW.json +0 -4
  126. package/package.json +3 -3
  127. package/src/BottomSheet/BottomSheet.doc.mjs +6 -0
  128. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  129. package/src/BottomSheet/BottomSheet.tsx +7 -4
  130. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  131. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  132. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  133. package/src/Button/Button.doc.mjs +56 -0
  134. package/src/Button/Button.test.tsx +10 -0
  135. package/src/Button/Button.tsx +13 -5
  136. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  137. package/src/Calendar/Calendar.doc.mjs +1 -1
  138. package/src/Carousel/Carousel.test.tsx +83 -0
  139. package/src/Carousel/Carousel.tsx +8 -2
  140. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  141. package/src/Chat/ChatToolCalls.tsx +36 -15
  142. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  143. package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
  144. package/src/ComplexSelector/ComplexSelector.doc.mjs +2 -2
  145. package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
  146. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  147. package/src/ContextMenu/ContextMenu.tsx +199 -32
  148. package/src/ContextMenu/index.ts +1 -0
  149. package/src/DateInput/DateInput.doc.mjs +1 -1
  150. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  151. package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
  152. package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
  153. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  154. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  155. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  156. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  157. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  158. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  159. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  160. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  161. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  162. package/src/DropdownMenu/index.ts +2 -0
  163. package/src/DropdownMenu/menuWidth.ts +51 -0
  164. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  165. package/src/Field/Field.doc.mjs +1 -1
  166. package/src/FieldStatus/FieldStatus.doc.mjs +1 -1
  167. package/src/FileInput/FileInput.doc.mjs +1 -1
  168. package/src/IconButton/IconButton.doc.mjs +38 -0
  169. package/src/Indicator/Indicator.doc.mjs +1 -1
  170. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  171. package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
  172. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  173. package/src/MoreMenu/MoreMenu.tsx +13 -1
  174. package/src/MultiSelector/MultiSelector.doc.mjs +17 -1
  175. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  176. package/src/MultiSelector/MultiSelector.tsx +121 -73
  177. package/src/MultiSelector/index.ts +1 -0
  178. package/src/NumberInput/NumberInput.doc.mjs +1 -1
  179. package/src/Popover/Popover.doc.mjs +11 -11
  180. package/src/Popover/Popover.test.tsx +397 -2
  181. package/src/Popover/Popover.tsx +228 -21
  182. package/src/Popover/usePopover.tsx +92 -15
  183. package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
  184. package/src/RadioList/RadioList.doc.mjs +1 -1
  185. package/src/SegmentedControl/SegmentedControl.doc.mjs +56 -0
  186. package/src/Selector/Selector.doc.mjs +13 -1
  187. package/src/Selector/Selector.test.tsx +135 -0
  188. package/src/Selector/Selector.tsx +141 -82
  189. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  190. package/src/Selector/index.ts +1 -0
  191. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  192. package/src/Selector/useSelectorPresentation.ts +147 -0
  193. package/src/Slider/Slider.doc.mjs +2 -2
  194. package/src/Switch/Switch.doc.mjs +1 -1
  195. package/src/TextArea/TextArea.doc.mjs +1 -1
  196. package/src/TextInput/TextInput.doc.mjs +1 -1
  197. package/src/TimeInput/TimeInput.doc.mjs +1 -1
  198. package/src/Toast/ToastViewport.test.tsx +118 -4
  199. package/src/Toast/useToastGesture.ts +61 -8
  200. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  201. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  202. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  203. package/src/hooks/useAdaptivePresentation.ts +35 -0
  204. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  205. package/src/hooks/useFocusTrap.test.tsx +40 -0
  206. package/src/hooks/useFocusTrap.ts +13 -0
  207. package/src/i18n/InternationalizationContext.ts +3 -0
  208. package/src/i18n/InternationalizationProvider.tsx +8 -1
  209. package/src/i18n/__tests__/resolve.test.ts +23 -1
  210. package/src/i18n/resolve.ts +46 -49
  211. package/src/i18n/useTranslator.ts +2 -7
@@ -0,0 +1,45 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useFocusReturnVisibility.ts
7
+ * @input Uses the shared interaction-modality tracker
8
+ * @output Preserves returned focus while suppressing pointer-only focus rings
9
+ * @position Internal focus-return policy for adaptive overlays
10
+ */
11
+
12
+ import {useCallback, useEffect, useState} from 'react';
13
+ import {
14
+ getInteractionModality,
15
+ trackInteractionModality,
16
+ } from '../utils/interactionModality';
17
+
18
+ export function useFocusReturnVisibility() {
19
+ const [isFocusRingSuppressed, setIsFocusRingSuppressed] = useState(false);
20
+
21
+ useEffect(() => {
22
+ trackInteractionModality();
23
+ }, []);
24
+
25
+ const prepareFocusReturn = useCallback(() => {
26
+ setIsFocusRingSuppressed(getInteractionModality() === 'pointer');
27
+ }, []);
28
+
29
+ const resetFocusReturn = useCallback(() => {
30
+ setIsFocusRingSuppressed(false);
31
+ }, []);
32
+
33
+ const onFocusReturnTargetFocus = useCallback(() => {
34
+ if (getInteractionModality() === 'keyboard') {
35
+ setIsFocusRingSuppressed(false);
36
+ }
37
+ }, []);
38
+
39
+ return {
40
+ isFocusRingSuppressed,
41
+ onFocusReturnTargetFocus,
42
+ prepareFocusReturn,
43
+ resetFocusReturn,
44
+ };
45
+ }
@@ -193,6 +193,46 @@ describe('useFocusTrap tabbable model (infra-8)', () => {
193
193
  expect(screen.getByTestId('first')).toHaveFocus();
194
194
  });
195
195
 
196
+ it('moves from a programmatic container focus target into the tabbable cycle', () => {
197
+ render(
198
+ <Trap>
199
+ <button type="button" data-testid="first">
200
+ First
201
+ </button>
202
+ <button type="button" data-testid="last">
203
+ Last
204
+ </button>
205
+ </Trap>,
206
+ );
207
+ const trap = screen.getByTestId('trap');
208
+ trap.tabIndex = -1;
209
+ trap.focus();
210
+
211
+ fireEvent.keyDown(document, {key: 'Tab'});
212
+
213
+ expect(screen.getByTestId('first')).toHaveFocus();
214
+ });
215
+
216
+ it('moves backward from a programmatic container focus target to the last tabbable control', () => {
217
+ render(
218
+ <Trap>
219
+ <button type="button" data-testid="first">
220
+ First
221
+ </button>
222
+ <button type="button" data-testid="last">
223
+ Last
224
+ </button>
225
+ </Trap>,
226
+ );
227
+ const trap = screen.getByTestId('trap');
228
+ trap.tabIndex = -1;
229
+ trap.focus();
230
+
231
+ fireEvent.keyDown(document, {key: 'Tab', shiftKey: true});
232
+
233
+ expect(screen.getByTestId('last')).toHaveFocus();
234
+ });
235
+
196
236
  it('keeps a programmatic focus target when the trap has no tabbable controls', () => {
197
237
  render(
198
238
  <Trap>
@@ -375,6 +375,19 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
375
375
  const first = focusable[0];
376
376
  const last = focusable[focusable.length - 1];
377
377
 
378
+ if (document.activeElement === container) {
379
+ // Programmatic initial focus can sit on the trap container itself
380
+ // (for example a dialog panel with no content controls). Move into
381
+ // the tabbable cycle instead of letting Tab escape to the page.
382
+ event.preventDefault();
383
+ if (event.shiftKey) {
384
+ last.focus();
385
+ } else {
386
+ first.focus();
387
+ }
388
+ return;
389
+ }
390
+
378
391
  if (event.shiftKey) {
379
392
  // Shift+Tab: if on first element, wrap to last
380
393
  if (document.activeElement === first) {
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import {createContext} from 'react';
26
+ import {getResolve} from './resolve';
26
27
  import type {Locale, MessagesByLocale, Overrides} from './types';
27
28
 
28
29
  export interface InternationalizationContextValue {
@@ -30,6 +31,7 @@ export interface InternationalizationContextValue {
30
31
  direction: 'ltr' | 'rtl';
31
32
  messages: MessagesByLocale;
32
33
  overrides?: Overrides;
34
+ translate: ReturnType<typeof getResolve>;
33
35
  }
34
36
 
35
37
  /**
@@ -41,5 +43,6 @@ export const InternationalizationContext =
41
43
  locale: 'en',
42
44
  direction: 'ltr',
43
45
  messages: {},
46
+ translate: getResolve('en', {}),
44
47
  });
45
48
  InternationalizationContext.displayName = 'InternationalizationContext';
@@ -23,6 +23,7 @@ import {useMemo, type ReactNode} from 'react';
23
23
  import {InternationalizationContext} from './InternationalizationContext';
24
24
  import {getLocaleDirection} from './getLocaleDirection';
25
25
  import type {Locale, MessagesByLocale, Overrides} from './types';
26
+ import {getResolve} from './resolve';
26
27
 
27
28
  export interface InternationalizationProviderProps {
28
29
  /**
@@ -85,7 +86,13 @@ export function InternationalizationProvider({
85
86
  }: InternationalizationProviderProps) {
86
87
  const direction = dir ?? getLocaleDirection(locale);
87
88
  const value = useMemo(
88
- () => ({locale, direction, messages: messages ?? {}, overrides}),
89
+ () => ({
90
+ locale,
91
+ direction,
92
+ messages: messages ?? {},
93
+ overrides,
94
+ translate: getResolve(locale, messages ?? {}, overrides),
95
+ }),
89
96
  [locale, direction, messages, overrides],
90
97
  );
91
98
  return (
@@ -9,9 +9,16 @@
9
9
  */
10
10
 
11
11
  import {describe, expect, test, beforeEach, vi} from 'vitest';
12
- import {__resetForTests, resolve, resolveLocaleChain} from '../resolve';
12
+ import {__resetForTests, getResolve, resolveLocaleChain} from '../resolve';
13
13
  import type {Catalog, MessagesByLocale, Overrides} from '../types';
14
14
 
15
+ const resolve = (
16
+ ...[key, values, locale, messages, overrides]: [
17
+ ...Parameters<ReturnType<typeof getResolve>>,
18
+ ...Parameters<typeof getResolve>,
19
+ ]
20
+ ) => getResolve(locale, messages, overrides)(key, values);
21
+
15
22
  // Reset caches between tests so warn-once and formatter cache don't bleed.
16
23
  beforeEach(() => {
17
24
  __resetForTests();
@@ -209,6 +216,21 @@ describe('resolve — overrides', () => {
209
216
  // Should fall through to en since fr has no messages and es override doesn't apply
210
217
  expect(out).toBe('Go to next page');
211
218
  });
219
+
220
+ test('override `pt` should take precedence over message `pt-BR`', () => {
221
+ const overrides: Overrides = {
222
+ pt: {key: 'override'},
223
+ };
224
+ const messages: MessagesByLocale = {
225
+ 'pt-BR': {
226
+ key: {
227
+ defaultMessage: 'shipped',
228
+ },
229
+ },
230
+ };
231
+ const out = resolve('key', undefined, 'pt-BR', messages, overrides);
232
+ expect(out).toBe('override');
233
+ });
212
234
  });
213
235
 
214
236
  describe('resolve — ICU MessageFormat', () => {
@@ -73,74 +73,71 @@ export function resolveLocaleChain(locale: Locale): Locale[] {
73
73
  return chain;
74
74
  }
75
75
 
76
- function lookup(
77
- key: string,
76
+ function getLookup(
78
77
  locale: Locale,
79
78
  messages: MessagesByLocale,
80
- overrides: Overrides | undefined,
81
- ): string | null {
79
+ overrides?: Overrides,
80
+ ): Record<string, string> {
81
+ const lookup: Record<string, string> = {};
82
82
  const chain = resolveLocaleChain(locale);
83
-
84
83
  // 1 + 2. Overrides (most specific to least specific in the chain)
85
84
  if (overrides !== undefined) {
86
85
  for (const tag of chain) {
87
- const value = overrides[tag]?.[key];
88
- if (value !== undefined) {
89
- return value;
86
+ if (overrides[tag]) {
87
+ for (const [key, value] of Object.entries(overrides[tag])) {
88
+ if (lookup[key] === undefined && value !== null) {
89
+ lookup[key] = value;
90
+ }
91
+ }
90
92
  }
91
93
  }
92
94
  }
93
-
94
- // 3 + 4. Shipped catalogs (most specific to least specific)
95
95
  for (const tag of chain) {
96
- const entry = messages[tag]?.[key];
97
- if (entry !== undefined) {
98
- return entry.defaultMessage;
96
+ if (messages[tag]) {
97
+ for (const [key, value] of Object.entries(messages[tag])) {
98
+ if (lookup[key] === undefined && value?.defaultMessage !== null) {
99
+ lookup[key] = value?.defaultMessage;
100
+ }
101
+ }
99
102
  }
100
103
  }
101
104
 
102
- // 5. Shipped en catalog — always present
103
- const enEntry = EN_CATALOG[key];
104
- if (enEntry !== undefined) {
105
- return enEntry.defaultMessage;
106
- }
107
-
108
- // 6. Nothing found
109
- return null;
105
+ return lookup;
110
106
  }
111
107
 
112
- export function resolve(
113
- key: string,
114
- values: Record<string, unknown> | undefined,
108
+ export function getResolve(
115
109
  locale: Locale,
116
110
  messages: MessagesByLocale,
117
- overrides: Overrides | undefined,
118
- ): string {
119
- const result = lookup(key, locale, messages, overrides);
120
-
121
- if (result === null) {
122
- // Fires ONLY when a key is missing from every source including the
123
- // shipped `en` catalog — a real bug (typo, stale catalog, deleted key).
124
- // Fallback to `en` from a non-en locale is expected and stays silent,
125
- // matching the FormatJS / i18next default.
126
- warnOnce(
127
- `astryx-i18n:${locale}::${key}`,
128
- 'astryx-i18n',
129
- `missing key: ${key} (locale: ${locale})`,
130
- );
131
- return key;
132
- }
111
+ overrides?: Overrides,
112
+ ) {
113
+ const lookup = getLookup(locale, messages, overrides);
114
+
115
+ return (key: string, values: Record<string, unknown> | undefined) => {
116
+ const result = lookup[key] ?? EN_CATALOG[key]?.defaultMessage;
117
+ if (result === undefined) {
118
+ // Fires ONLY when a key is missing from every source including the
119
+ // shipped `en` catalog — a real bug (typo, stale catalog, deleted key).
120
+ // Fallback to `en` from a non-en locale is expected and stays silent,
121
+ // matching the FormatJS / i18next default.
122
+ warnOnce(
123
+ `astryx-i18n:${locale}::${key}`,
124
+ 'astryx-i18n',
125
+ `missing key: ${key} (locale: ${locale})`,
126
+ );
127
+ return key;
128
+ }
133
129
 
134
- if (values === undefined) {
135
- // Static string — skip the parser entirely for the common case
136
- return result;
137
- }
130
+ if (values === undefined) {
131
+ // Static string — skip the parser entirely for the common case
132
+ return result;
133
+ }
138
134
 
139
- const formatted = getFormatter(result, locale).format(values);
140
- // IntlMessageFormat.format returns string | (string | React elements) — we
141
- // only ever pass string values so it will be a string; assert for the type
142
- // system.
143
- return formatted as string;
135
+ const formatted = getFormatter(result, locale).format(values);
136
+ // IntlMessageFormat.format returns string | (string | React elements) — we
137
+ // only ever pass string values so it will be a string; assert for the type
138
+ // system.
139
+ return formatted as string;
140
+ };
144
141
  }
145
142
 
146
143
  /**
@@ -19,9 +19,8 @@
19
19
  * - /packages/core/src/i18n/t.client.ts
20
20
  */
21
21
 
22
- import {use, useCallback} from 'react';
22
+ import {use} from 'react';
23
23
  import {InternationalizationContext} from './InternationalizationContext';
24
- import {resolve} from './resolve';
25
24
 
26
25
  export type TranslatorFn = (
27
26
  key: string,
@@ -42,9 +41,5 @@ export type TranslatorFn = (
42
41
  */
43
42
  export function useTranslator(): TranslatorFn {
44
43
  const ctx = use(InternationalizationContext);
45
- return useCallback(
46
- (key: string, values?: Record<string, unknown>) =>
47
- resolve(key, values, ctx.locale, ctx.messages, ctx.overrides),
48
- [ctx.locale, ctx.messages, ctx.overrides],
49
- );
44
+ return ctx.translate;
50
45
  }