@oxy.so/bloom 4.32.0 → 4.33.0

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 (173) hide show
  1. package/docs/adoption-matrix.mdx +4 -3
  2. package/docs/date-picker.mdx +15 -28
  3. package/docs/locale.mdx +46 -0
  4. package/docs/provider.mdx +11 -0
  5. package/lib/commonjs/calendar/shared.js +5 -11
  6. package/lib/commonjs/calendar/shared.js.map +1 -1
  7. package/lib/commonjs/date-picker/Calendar.js +11 -2
  8. package/lib/commonjs/date-picker/Calendar.js.map +1 -1
  9. package/lib/commonjs/date-picker/CalendarMonth.js +4 -3
  10. package/lib/commonjs/date-picker/CalendarMonth.js.map +1 -1
  11. package/lib/commonjs/date-picker/DatePicker.js +13 -4
  12. package/lib/commonjs/date-picker/DatePicker.js.map +1 -1
  13. package/lib/commonjs/date-picker/DateRangePicker.js +17 -13
  14. package/lib/commonjs/date-picker/DateRangePicker.js.map +1 -1
  15. package/lib/commonjs/date-picker/MeetingScheduler.js +18 -13
  16. package/lib/commonjs/date-picker/MeetingScheduler.js.map +1 -1
  17. package/lib/commonjs/date-picker/PickerShell.js +5 -5
  18. package/lib/commonjs/date-picker/PickerShell.js.map +1 -1
  19. package/lib/commonjs/date-picker/calendar-grid.js +7 -21
  20. package/lib/commonjs/date-picker/calendar-grid.js.map +1 -1
  21. package/lib/commonjs/date-picker/messages.js +526 -0
  22. package/lib/commonjs/date-picker/messages.js.map +1 -0
  23. package/lib/commonjs/index.js +1063 -1034
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/index.web.js +1063 -1034
  26. package/lib/commonjs/index.web.js.map +1 -1
  27. package/lib/commonjs/locale/context.js +35 -0
  28. package/lib/commonjs/locale/context.js.map +1 -0
  29. package/lib/commonjs/locale/format-date.js +34 -0
  30. package/lib/commonjs/locale/format-date.js.map +1 -0
  31. package/lib/commonjs/locale/index.js +32 -0
  32. package/lib/commonjs/locale/index.js.map +1 -0
  33. package/lib/commonjs/locale/languages.js +35 -0
  34. package/lib/commonjs/locale/languages.js.map +1 -0
  35. package/lib/commonjs/locale/messages.js +34 -0
  36. package/lib/commonjs/locale/messages.js.map +1 -0
  37. package/lib/commonjs/locale/plural.js +53 -0
  38. package/lib/commonjs/locale/plural.js.map +1 -0
  39. package/lib/commonjs/provider/BloomProvider.js +18 -13
  40. package/lib/commonjs/provider/BloomProvider.js.map +1 -1
  41. package/lib/module/calendar/shared.js +5 -11
  42. package/lib/module/calendar/shared.js.map +1 -1
  43. package/lib/module/date-picker/Calendar.js +11 -2
  44. package/lib/module/date-picker/Calendar.js.map +1 -1
  45. package/lib/module/date-picker/CalendarMonth.js +4 -3
  46. package/lib/module/date-picker/CalendarMonth.js.map +1 -1
  47. package/lib/module/date-picker/DatePicker.js +13 -4
  48. package/lib/module/date-picker/DatePicker.js.map +1 -1
  49. package/lib/module/date-picker/DateRangePicker.js +17 -13
  50. package/lib/module/date-picker/DateRangePicker.js.map +1 -1
  51. package/lib/module/date-picker/MeetingScheduler.js +18 -13
  52. package/lib/module/date-picker/MeetingScheduler.js.map +1 -1
  53. package/lib/module/date-picker/PickerShell.js +6 -4
  54. package/lib/module/date-picker/PickerShell.js.map +1 -1
  55. package/lib/module/date-picker/calendar-grid.js +7 -21
  56. package/lib/module/date-picker/calendar-grid.js.map +1 -1
  57. package/lib/module/date-picker/messages.js +523 -0
  58. package/lib/module/date-picker/messages.js.map +1 -0
  59. package/lib/module/index.js +1 -0
  60. package/lib/module/index.js.map +1 -1
  61. package/lib/module/index.web.js +1 -0
  62. package/lib/module/index.web.js.map +1 -1
  63. package/lib/module/locale/context.js +30 -0
  64. package/lib/module/locale/context.js.map +1 -0
  65. package/lib/module/locale/format-date.js +30 -0
  66. package/lib/module/locale/format-date.js.map +1 -0
  67. package/lib/module/locale/index.js +7 -0
  68. package/lib/module/locale/index.js.map +1 -0
  69. package/lib/module/locale/languages.js +29 -0
  70. package/lib/module/locale/languages.js.map +1 -0
  71. package/lib/module/locale/messages.js +30 -0
  72. package/lib/module/locale/messages.js.map +1 -0
  73. package/lib/module/locale/plural.js +48 -0
  74. package/lib/module/locale/plural.js.map +1 -0
  75. package/lib/module/provider/BloomProvider.js +16 -11
  76. package/lib/module/provider/BloomProvider.js.map +1 -1
  77. package/lib/typescript/commonjs/calendar/shared.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/date-picker/Calendar.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/date-picker/CalendarMonth.d.ts +5 -3
  80. package/lib/typescript/commonjs/date-picker/CalendarMonth.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/date-picker/DatePicker.d.ts +1 -1
  82. package/lib/typescript/commonjs/date-picker/DatePicker.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/date-picker/DateRangePicker.d.ts +1 -1
  84. package/lib/typescript/commonjs/date-picker/DateRangePicker.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/date-picker/MeetingScheduler.d.ts +1 -1
  86. package/lib/typescript/commonjs/date-picker/MeetingScheduler.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/date-picker/PickerShell.d.ts +3 -3
  88. package/lib/typescript/commonjs/date-picker/PickerShell.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/date-picker/calendar-grid.d.ts +1 -2
  90. package/lib/typescript/commonjs/date-picker/calendar-grid.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/date-picker/messages.d.ts +31 -0
  92. package/lib/typescript/commonjs/date-picker/messages.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/date-picker/types.d.ts +28 -24
  94. package/lib/typescript/commonjs/date-picker/types.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/index.d.ts +2 -0
  96. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/index.web.d.ts +2 -0
  98. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/locale/context.d.ts +18 -0
  100. package/lib/typescript/commonjs/locale/context.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/locale/format-date.d.ts +13 -0
  102. package/lib/typescript/commonjs/locale/format-date.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/locale/index.d.ts +3 -0
  104. package/lib/typescript/commonjs/locale/index.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/locale/languages.d.ts +16 -0
  106. package/lib/typescript/commonjs/locale/languages.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/locale/messages.d.ts +18 -0
  108. package/lib/typescript/commonjs/locale/messages.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/locale/plural.d.ts +19 -0
  110. package/lib/typescript/commonjs/locale/plural.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/provider/BloomProvider.d.ts +1 -1
  112. package/lib/typescript/commonjs/provider/BloomProvider.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/provider/types.d.ts +6 -0
  114. package/lib/typescript/commonjs/provider/types.d.ts.map +1 -1
  115. package/lib/typescript/module/calendar/shared.d.ts.map +1 -1
  116. package/lib/typescript/module/date-picker/Calendar.d.ts.map +1 -1
  117. package/lib/typescript/module/date-picker/CalendarMonth.d.ts +5 -3
  118. package/lib/typescript/module/date-picker/CalendarMonth.d.ts.map +1 -1
  119. package/lib/typescript/module/date-picker/DatePicker.d.ts +1 -1
  120. package/lib/typescript/module/date-picker/DatePicker.d.ts.map +1 -1
  121. package/lib/typescript/module/date-picker/DateRangePicker.d.ts +1 -1
  122. package/lib/typescript/module/date-picker/DateRangePicker.d.ts.map +1 -1
  123. package/lib/typescript/module/date-picker/MeetingScheduler.d.ts +1 -1
  124. package/lib/typescript/module/date-picker/MeetingScheduler.d.ts.map +1 -1
  125. package/lib/typescript/module/date-picker/PickerShell.d.ts +3 -3
  126. package/lib/typescript/module/date-picker/PickerShell.d.ts.map +1 -1
  127. package/lib/typescript/module/date-picker/calendar-grid.d.ts +1 -2
  128. package/lib/typescript/module/date-picker/calendar-grid.d.ts.map +1 -1
  129. package/lib/typescript/module/date-picker/messages.d.ts +31 -0
  130. package/lib/typescript/module/date-picker/messages.d.ts.map +1 -0
  131. package/lib/typescript/module/date-picker/types.d.ts +28 -24
  132. package/lib/typescript/module/date-picker/types.d.ts.map +1 -1
  133. package/lib/typescript/module/index.d.ts +2 -0
  134. package/lib/typescript/module/index.d.ts.map +1 -1
  135. package/lib/typescript/module/index.web.d.ts +2 -0
  136. package/lib/typescript/module/index.web.d.ts.map +1 -1
  137. package/lib/typescript/module/locale/context.d.ts +18 -0
  138. package/lib/typescript/module/locale/context.d.ts.map +1 -0
  139. package/lib/typescript/module/locale/format-date.d.ts +13 -0
  140. package/lib/typescript/module/locale/format-date.d.ts.map +1 -0
  141. package/lib/typescript/module/locale/index.d.ts +3 -0
  142. package/lib/typescript/module/locale/index.d.ts.map +1 -0
  143. package/lib/typescript/module/locale/languages.d.ts +16 -0
  144. package/lib/typescript/module/locale/languages.d.ts.map +1 -0
  145. package/lib/typescript/module/locale/messages.d.ts +18 -0
  146. package/lib/typescript/module/locale/messages.d.ts.map +1 -0
  147. package/lib/typescript/module/locale/plural.d.ts +19 -0
  148. package/lib/typescript/module/locale/plural.d.ts.map +1 -0
  149. package/lib/typescript/module/provider/BloomProvider.d.ts +1 -1
  150. package/lib/typescript/module/provider/BloomProvider.d.ts.map +1 -1
  151. package/lib/typescript/module/provider/types.d.ts +6 -0
  152. package/lib/typescript/module/provider/types.d.ts.map +1 -1
  153. package/package.json +18 -1
  154. package/src/calendar/shared.ts +5 -12
  155. package/src/date-picker/Calendar.tsx +8 -2
  156. package/src/date-picker/CalendarMonth.tsx +8 -5
  157. package/src/date-picker/DatePicker.tsx +10 -4
  158. package/src/date-picker/DateRangePicker.tsx +14 -13
  159. package/src/date-picker/MeetingScheduler.tsx +17 -11
  160. package/src/date-picker/PickerShell.tsx +6 -5
  161. package/src/date-picker/calendar-grid.ts +16 -23
  162. package/src/date-picker/messages.ts +470 -0
  163. package/src/date-picker/types.ts +28 -24
  164. package/src/index.ts +2 -0
  165. package/src/index.web.ts +2 -0
  166. package/src/locale/context.ts +23 -0
  167. package/src/locale/format-date.ts +28 -0
  168. package/src/locale/index.ts +4 -0
  169. package/src/locale/languages.ts +45 -0
  170. package/src/locale/messages.ts +29 -0
  171. package/src/locale/plural.ts +53 -0
  172. package/src/provider/BloomProvider.tsx +39 -35
  173. package/src/provider/types.ts +6 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The languages Bloom ships its own strings in — the fleet's app languages.
3
+ * A `MessageCatalog` must name every one of them, so adding a language here
4
+ * fails the type-check of every catalog until it is translated.
5
+ */
6
+ export const BLOOM_LANGUAGES = [
7
+ 'en',
8
+ 'es',
9
+ 'ca',
10
+ 'de',
11
+ 'fr',
12
+ 'it',
13
+ 'pt',
14
+ 'ru',
15
+ 'tr',
16
+ 'ja',
17
+ 'zh',
18
+ 'ar',
19
+ 'hi',
20
+ 'bn',
21
+ 'id',
22
+ ] as const;
23
+
24
+ export type BloomLanguage = (typeof BLOOM_LANGUAGES)[number];
25
+
26
+ const SUPPORTED = new Set<string>(BLOOM_LANGUAGES);
27
+
28
+ /**
29
+ * The language a BCP 47 tag asks for, among Bloom's: its primary subtag
30
+ * (`es-MX` → `es`, `pt_BR` → `pt`, `ca-ES-valencia` → `ca`). Anything Bloom
31
+ * does not ship, and no tag at all, falls back to English.
32
+ */
33
+ export function resolveBloomLanguage(locale: string | null | undefined): BloomLanguage {
34
+ const primary = locale?.split(/[-_]/)[0]?.toLowerCase();
35
+ return primary && SUPPORTED.has(primary) ? (primary as BloomLanguage) : 'en';
36
+ }
37
+
38
+ /** The runtime's own locale (the browser's, or the device's through Hermes' `Intl`). */
39
+ export function runtimeLocale(): string | undefined {
40
+ try {
41
+ return new Intl.DateTimeFormat().resolvedOptions().locale;
42
+ } catch {
43
+ return undefined;
44
+ }
45
+ }
@@ -0,0 +1,29 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useBloomLocale } from './context';
4
+ import { resolveBloomLanguage, runtimeLocale, type BloomLanguage } from './languages';
5
+
6
+ /**
7
+ * A family's fixed strings in every Bloom language. The type makes each
8
+ * language REQUIRED and complete, so a missing translation is a type error
9
+ * rather than an English word inside a translated screen.
10
+ */
11
+ export type MessageCatalog<M> = Readonly<Record<BloomLanguage, M>>;
12
+
13
+ /** The catalog entry for `locale`, falling back to English for a language Bloom does not ship. */
14
+ export function pickMessages<M>(catalog: MessageCatalog<M>, locale: string | undefined): M {
15
+ return catalog[resolveBloomLanguage(locale ?? runtimeLocale())];
16
+ }
17
+
18
+ /**
19
+ * The resolved locale (prop → `LocaleProvider` → runtime) and the family's
20
+ * strings in it. A component still lets its `labels` prop win over these.
21
+ */
22
+ export function useMessages<M>(
23
+ catalog: MessageCatalog<M>,
24
+ localeProp?: string,
25
+ ): { locale: string | undefined; messages: M } {
26
+ const locale = useBloomLocale(localeProp);
27
+ const messages = useMemo(() => pickMessages(catalog, locale), [catalog, locale]);
28
+ return { locale, messages };
29
+ }
@@ -0,0 +1,53 @@
1
+ import type { BloomLanguage } from './languages';
2
+
3
+ export type PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
4
+
5
+ /**
6
+ * The CLDR cardinal category of a whole number, for Bloom's languages.
7
+ *
8
+ * Written out rather than read from `Intl.PluralRules` because Hermes — React
9
+ * Native's engine — does not implement `PluralRules`: a catalog that called it
10
+ * would pluralise on web and throw on a phone. Whole numbers only; a count of
11
+ * days or minutes is never fractional.
12
+ */
13
+ export function pluralCategory(language: BloomLanguage, count: number): PluralCategory {
14
+ const n = Math.abs(Math.trunc(count));
15
+ const mod10 = n % 10;
16
+ const mod100 = n % 100;
17
+ switch (language) {
18
+ case 'ja':
19
+ case 'zh':
20
+ case 'id':
21
+ return 'other';
22
+ case 'fr':
23
+ case 'pt':
24
+ case 'hi':
25
+ case 'bn':
26
+ return n === 0 || n === 1 ? 'one' : 'other';
27
+ case 'ru':
28
+ if (mod10 === 1 && mod100 !== 11) return 'one';
29
+ if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return 'few';
30
+ return 'many';
31
+ case 'ar':
32
+ if (n === 0) return 'zero';
33
+ if (n === 1) return 'one';
34
+ if (n === 2) return 'two';
35
+ if (mod100 >= 3 && mod100 <= 10) return 'few';
36
+ if (mod100 >= 11) return 'many';
37
+ return 'other';
38
+ default:
39
+ return n === 1 ? 'one' : 'other';
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Picks the form for `count`. `other` is required; a category a language
45
+ * uses but the caller left out falls back to it.
46
+ */
47
+ export function plural(
48
+ language: BloomLanguage,
49
+ count: number,
50
+ forms: Partial<Record<PluralCategory, string>> & { other: string },
51
+ ): string {
52
+ return (forms[pluralCategory(language, count)] ?? forms.other).replace('{n}', String(count));
53
+ }
@@ -7,6 +7,7 @@ import { GlassBlurTargetProvider } from '../glass/blur-target';
7
7
  import { BottomEdgeProvider } from '../layout/bottom-edge';
8
8
  import { TopEdgeProvider } from '../layout/top-edge';
9
9
  import { ImageResolverProvider, type ImageResolver } from '../image-resolver';
10
+ import { LocaleProvider } from '../locale/context';
10
11
  import { BloomHapticsProvider } from '../hooks/use-haptics';
11
12
  import { TabBarMinimizeProvider } from '../tab-bar/context';
12
13
  import { BloomThemeProvider, type BloomThemeProviderProps } from '../theme';
@@ -26,51 +27,54 @@ export function BloomProvider({
26
27
  imageResolver,
27
28
  haptics = true,
28
29
  scrollAdapter = noRouterAdapter,
30
+ locale,
29
31
  ...themeProps
30
32
  }: BloomProviderProps) {
31
33
  return (
32
34
  // `value` is passed unconditionally (null when unset) so toggling a resolver
33
35
  // never changes the tree shape and remounts everything below it.
34
36
  <ImageResolverProvider value={imageResolver ?? null}>
35
- <BloomThemeProvider {...themeProps}>
36
- <ScrollRestorationProvider adapter={scrollAdapter}>
37
- <BloomHapticsProvider enabled={haptics}>
38
- {/*
39
- A PROVIDER, not an outlet, and the distinction is the one this
40
- component's exclusion list encodes. Outlets stay out because a
41
- second mount duplicates every surface they render; this renders NO
42
- surface — a transparent container on Android and nothing at all
43
- elsewhere — so a second mount duplicates nothing, and the nearest
44
- one simply becomes the target for its subtree. Nor is its position
45
- an app decision the way an outlet's is: it has no z-order and no
46
- insets, and it must WRAP the content, which is exactly what this
47
- provider already does with `children`.
48
- */}
49
- <GlassBlurTargetProvider>
37
+ <LocaleProvider locale={locale}>
38
+ <BloomThemeProvider {...themeProps}>
39
+ <ScrollRestorationProvider adapter={scrollAdapter}>
40
+ <BloomHapticsProvider enabled={haptics}>
50
41
  {/*
51
- The bottom edge's claim registry. Also a provider, not an outlet:
52
- it renders nothing and holds only the set of surfaces currently
53
- parked at the bottom edge, so a tab bar can publish its footprint
54
- and a FAB or a toast stack can read it without either importing
55
- the other.
42
+ A PROVIDER, not an outlet, and the distinction is the one this
43
+ component's exclusion list encodes. Outlets stay out because a
44
+ second mount duplicates every surface they render; this renders NO
45
+ surface — a transparent container on Android and nothing at all
46
+ elsewhere — so a second mount duplicates nothing, and the nearest
47
+ one simply becomes the target for its subtree. Nor is its position
48
+ an app decision the way an outlet's is: it has no z-order and no
49
+ insets, and it must WRAP the content, which is exactly what this
50
+ provider already does with `children`.
56
51
  */}
57
- <BottomEdgeProvider>
52
+ <GlassBlurTargetProvider>
58
53
  {/*
59
- And the top edge's, for the same reason one edge further up: a
60
- floating `PageHeader` overlays the content rather than sitting
61
- above it, so the content has to be told how much of the edge is
62
- gone. Two registries rather than one keyed by edge, because a
63
- reader of either only ever wants one number and must not
64
- re-render when the other moves.
54
+ The bottom edge's claim registry. Also a provider, not an outlet:
55
+ it renders nothing and holds only the set of surfaces currently
56
+ parked at the bottom edge, so a tab bar can publish its footprint
57
+ and a FAB or a toast stack can read it without either importing
58
+ the other.
65
59
  */}
66
- <TopEdgeProvider>
67
- <TabBarMinimizeProvider>{children}</TabBarMinimizeProvider>
68
- </TopEdgeProvider>
69
- </BottomEdgeProvider>
70
- </GlassBlurTargetProvider>
71
- </BloomHapticsProvider>
72
- </ScrollRestorationProvider>
73
- </BloomThemeProvider>
60
+ <BottomEdgeProvider>
61
+ {/*
62
+ And the top edge's, for the same reason one edge further up: a
63
+ floating `PageHeader` overlays the content rather than sitting
64
+ above it, so the content has to be told how much of the edge is
65
+ gone. Two registries rather than one keyed by edge, because a
66
+ reader of either only ever wants one number and must not
67
+ re-render when the other moves.
68
+ */}
69
+ <TopEdgeProvider>
70
+ <TabBarMinimizeProvider>{children}</TabBarMinimizeProvider>
71
+ </TopEdgeProvider>
72
+ </BottomEdgeProvider>
73
+ </GlassBlurTargetProvider>
74
+ </BloomHapticsProvider>
75
+ </ScrollRestorationProvider>
76
+ </BloomThemeProvider>
77
+ </LocaleProvider>
74
78
  </ImageResolverProvider>
75
79
  );
76
80
  }
@@ -15,4 +15,10 @@ export interface BloomProviderProps extends Omit<BloomThemeProviderProps, 'child
15
15
  imageResolver?: ImageResolver;
16
16
  /** `false` disables haptic feedback app-wide (honored by every `useHaptics()` call). */
17
17
  haptics?: boolean;
18
+ /**
19
+ * BCP 47 locale for every Bloom component below — dates, month names and
20
+ * Bloom's own strings (Cancel, Apply, …). A component's own `locale` prop
21
+ * wins. Unset, components follow the runtime's locale.
22
+ */
23
+ locale?: string;
18
24
  }