@byline/host-tanstack-start 2.5.2 → 2.6.1

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 (123) hide show
  1. package/dist/admin-shell/admin-roles/container.js +38 -24
  2. package/dist/admin-shell/admin-roles/delete.js +9 -7
  3. package/dist/admin-shell/admin-roles/list.js +20 -16
  4. package/dist/admin-shell/admin-users/container.js +79 -56
  5. package/dist/admin-shell/admin-users/delete.js +10 -8
  6. package/dist/admin-shell/admin-users/list.js +27 -18
  7. package/dist/admin-shell/chrome/admin-app-bar.js +5 -2
  8. package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs.js +3 -1
  9. package/dist/admin-shell/chrome/dashboard.js +13 -11
  10. package/dist/admin-shell/chrome/hamburger.js +3 -1
  11. package/dist/admin-shell/chrome/menu-drawer.js +7 -5
  12. package/dist/admin-shell/chrome/preview-toggle.js +5 -3
  13. package/dist/admin-shell/chrome/route-error.d.ts +3 -2
  14. package/dist/admin-shell/chrome/route-error.js +29 -22
  15. package/dist/admin-shell/chrome/sign-in-page.d.ts +16 -4
  16. package/dist/admin-shell/chrome/sign-in-page.js +38 -13
  17. package/dist/admin-shell/chrome/sign-in-page.module.js +1 -0
  18. package/dist/admin-shell/chrome/sign-in-page_module.css +8 -1
  19. package/dist/admin-shell/collections/api.js +6 -5
  20. package/dist/admin-shell/collections/create.js +12 -4
  21. package/dist/admin-shell/collections/edit.js +112 -37
  22. package/dist/admin-shell/collections/history.js +17 -12
  23. package/dist/admin-shell/collections/list.js +18 -13
  24. package/dist/admin-shell/collections/preview-link.d.ts +1 -10
  25. package/dist/admin-shell/collections/preview-link.js +9 -11
  26. package/dist/admin-shell/collections/resolve-preview-url.d.ts +34 -0
  27. package/dist/admin-shell/collections/resolve-preview-url.js +17 -0
  28. package/dist/admin-shell/collections/restore-version-modal.js +13 -14
  29. package/dist/admin-shell/collections/tanstack-navigation-guard.d.ts +1 -1
  30. package/dist/admin-shell/collections/view-menu.js +7 -5
  31. package/dist/i18n/index.d.ts +19 -0
  32. package/dist/i18n/index.js +4 -0
  33. package/dist/i18n/locale-cookie.d.ts +17 -0
  34. package/dist/i18n/locale-cookie.js +26 -0
  35. package/dist/i18n/locale-definitions.d.ts +29 -0
  36. package/dist/i18n/locale-definitions.js +27 -0
  37. package/dist/i18n/resolve-locale.d.ts +20 -0
  38. package/dist/i18n/resolve-locale.js +43 -0
  39. package/dist/i18n/server-translator.d.ts +33 -0
  40. package/dist/i18n/server-translator.js +19 -0
  41. package/dist/integrations/byline-admin-services.js +2 -0
  42. package/dist/integrations/byline-field-services.d.ts +3 -3
  43. package/dist/routes/create-admin-account-route.js +6 -3
  44. package/dist/routes/create-admin-dashboard-route.js +3 -1
  45. package/dist/routes/create-admin-layout-route.js +48 -25
  46. package/dist/routes/create-admin-permissions-route.js +4 -2
  47. package/dist/routes/create-admin-role-edit-route.js +5 -3
  48. package/dist/routes/create-admin-roles-list-route.js +4 -2
  49. package/dist/routes/create-admin-user-edit-route.js +5 -3
  50. package/dist/routes/create-admin-users-list-route.js +4 -2
  51. package/dist/routes/create-collection-api-route.js +5 -3
  52. package/dist/routes/create-collection-create-route.js +4 -2
  53. package/dist/routes/create-collection-edit-route.js +4 -2
  54. package/dist/routes/create-collection-history-route.js +5 -3
  55. package/dist/routes/create-collection-list-route.js +11 -5
  56. package/dist/routes/create-sign-in-route.js +10 -1
  57. package/dist/server-fns/admin-account/change-password.d.ts +1 -0
  58. package/dist/server-fns/admin-account/get.d.ts +1 -0
  59. package/dist/server-fns/admin-account/update.d.ts +1 -0
  60. package/dist/server-fns/admin-users/create.d.ts +1 -0
  61. package/dist/server-fns/admin-users/get.d.ts +1 -0
  62. package/dist/server-fns/admin-users/list.d.ts +1 -0
  63. package/dist/server-fns/admin-users/set-password.d.ts +1 -0
  64. package/dist/server-fns/admin-users/update.d.ts +1 -0
  65. package/dist/server-fns/auth/sign-in.js +18 -0
  66. package/dist/server-fns/i18n/get-active-locale.d.ts +8 -0
  67. package/dist/server-fns/i18n/get-active-locale.js +6 -0
  68. package/dist/server-fns/i18n/index.d.ts +10 -0
  69. package/dist/server-fns/i18n/index.js +2 -0
  70. package/dist/server-fns/i18n/set-locale.d.ts +25 -0
  71. package/dist/server-fns/i18n/set-locale.js +42 -0
  72. package/package.json +16 -7
  73. package/src/admin-shell/admin-roles/container.tsx +41 -31
  74. package/src/admin-shell/admin-roles/delete.tsx +10 -11
  75. package/src/admin-shell/admin-roles/list.tsx +29 -16
  76. package/src/admin-shell/admin-users/container.tsx +77 -50
  77. package/src/admin-shell/admin-users/delete.tsx +11 -12
  78. package/src/admin-shell/admin-users/list.tsx +39 -18
  79. package/src/admin-shell/chrome/admin-app-bar.tsx +5 -2
  80. package/src/admin-shell/chrome/breadcrumbs/breadcrumbs.tsx +3 -1
  81. package/src/admin-shell/chrome/dashboard.tsx +9 -3
  82. package/src/admin-shell/chrome/hamburger.tsx +3 -1
  83. package/src/admin-shell/chrome/menu-drawer.tsx +7 -5
  84. package/src/admin-shell/chrome/preview-toggle.tsx +6 -4
  85. package/src/admin-shell/chrome/route-error.tsx +39 -26
  86. package/src/admin-shell/chrome/sign-in-page.module.css +10 -1
  87. package/src/admin-shell/chrome/sign-in-page.tsx +46 -12
  88. package/src/admin-shell/collections/api.tsx +5 -1
  89. package/src/admin-shell/collections/create.tsx +10 -4
  90. package/src/admin-shell/collections/edit.tsx +79 -72
  91. package/src/admin-shell/collections/history.tsx +18 -12
  92. package/src/admin-shell/collections/list.tsx +25 -14
  93. package/src/admin-shell/collections/preview-link.tsx +20 -33
  94. package/src/admin-shell/collections/resolve-preview-url.test.node.ts +167 -0
  95. package/src/admin-shell/collections/resolve-preview-url.ts +67 -0
  96. package/src/admin-shell/collections/restore-version-modal.tsx +11 -12
  97. package/src/admin-shell/collections/tanstack-navigation-guard.ts +1 -1
  98. package/src/admin-shell/collections/view-menu.tsx +9 -5
  99. package/src/i18n/index.ts +26 -0
  100. package/src/i18n/locale-cookie.ts +68 -0
  101. package/src/i18n/locale-definitions.ts +48 -0
  102. package/src/i18n/resolve-locale.ts +96 -0
  103. package/src/i18n/server-translator.ts +60 -0
  104. package/src/integrations/byline-admin-services.ts +2 -0
  105. package/src/integrations/byline-field-services.ts +7 -3
  106. package/src/routes/create-admin-account-route.tsx +6 -4
  107. package/src/routes/create-admin-dashboard-route.tsx +5 -1
  108. package/src/routes/create-admin-layout-route.tsx +53 -20
  109. package/src/routes/create-admin-permissions-route.tsx +4 -2
  110. package/src/routes/create-admin-role-edit-route.tsx +5 -3
  111. package/src/routes/create-admin-roles-list-route.tsx +5 -2
  112. package/src/routes/create-admin-user-edit-route.tsx +5 -3
  113. package/src/routes/create-admin-users-list-route.tsx +4 -2
  114. package/src/routes/create-collection-api-route.tsx +5 -3
  115. package/src/routes/create-collection-create-route.tsx +7 -2
  116. package/src/routes/create-collection-edit-route.tsx +4 -2
  117. package/src/routes/create-collection-history-route.tsx +5 -3
  118. package/src/routes/create-collection-list-route.tsx +8 -10
  119. package/src/routes/create-sign-in-route.tsx +14 -1
  120. package/src/server-fns/auth/sign-in.ts +45 -0
  121. package/src/server-fns/i18n/get-active-locale.ts +26 -0
  122. package/src/server-fns/i18n/index.ts +11 -0
  123. package/src/server-fns/i18n/set-locale.ts +103 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type React from 'react'
4
4
 
5
+ import { useTranslation } from '@byline/i18n/react'
5
6
  import cx from 'classnames'
6
7
 
7
8
  import styles from './hamburger.module.css'
@@ -21,6 +22,7 @@ export function Hamburger({
21
22
  ...other
22
23
  }: HamburgerProps): React.JSX.Element {
23
24
  const { toggleDrawer } = useAdminMenu()
25
+ const { t } = useTranslation('byline-admin')
24
26
 
25
27
  // Hard code the drawer hamburger to always closed
26
28
  // to prevent the 'X' pattern from being the default
@@ -43,7 +45,7 @@ export function Hamburger({
43
45
  className
44
46
  )}
45
47
  tabIndex={0}
46
- aria-label="Open admin menu"
48
+ aria-label={t('chrome.menu.openAriaLabel')}
47
49
  aria-controls="admin-menu"
48
50
  aria-haspopup="true"
49
51
  {...other}
@@ -12,6 +12,7 @@ import { useRouterState } from '@tanstack/react-router'
12
12
  import { ADMIN_PERMISSIONS_ABILITIES } from '@byline/admin/admin-permissions'
13
13
  import { ADMIN_ROLES_ABILITIES } from '@byline/admin/admin-roles'
14
14
  import { ADMIN_USERS_ABILITIES } from '@byline/admin/admin-users'
15
+ import { useTranslation } from '@byline/i18n/react'
15
16
  import { HomeIcon, RolesIcon, SettingsSlidersIcon, UserIcon, UsersIcon } from '@byline/ui/react'
16
17
  import cx from 'classnames'
17
18
  import { useSwipeable } from 'react-swipeable'
@@ -51,6 +52,7 @@ function MenuItem({ to, label, icon, pathname, compact }: MenuItemProps) {
51
52
  export function AdminMenuDrawer(): React.JSX.Element | null {
52
53
  const pathname = useRouterState({ select: (s) => s.location.pathname })
53
54
  const { mobile, drawerOpen, closeDrawer } = useAdminMenu()
55
+ const { t } = useTranslation('byline-admin')
54
56
 
55
57
  // Cosmetic ability cues — hide the admin-management section entirely
56
58
  // when the user holds none of its read abilities. Server-side
@@ -96,7 +98,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
96
98
  <ul>
97
99
  <MenuItem
98
100
  to="/admin"
99
- label="Dashboard"
101
+ label={t('chrome.menu.dashboard')}
100
102
  icon={<HomeIcon width="20px" height="20px" />}
101
103
  pathname={pathname}
102
104
  compact={compact}
@@ -107,7 +109,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
107
109
  {canReadUsers && (
108
110
  <MenuItem
109
111
  to="/admin/users"
110
- label="Admin Users"
112
+ label={t('chrome.menu.adminUsers')}
111
113
  icon={<UsersIcon width="20px" height="20px" />}
112
114
  pathname={pathname}
113
115
  compact={compact}
@@ -116,7 +118,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
116
118
  {canReadRoles && (
117
119
  <MenuItem
118
120
  to="/admin/roles"
119
- label="Admin Roles"
121
+ label={t('chrome.menu.adminRoles')}
120
122
  icon={<RolesIcon width="20px" height="20px" />}
121
123
  pathname={pathname}
122
124
  compact={compact}
@@ -125,7 +127,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
125
127
  {canReadPermissions && (
126
128
  <MenuItem
127
129
  to="/admin/permissions"
128
- label="Permissions"
130
+ label={t('chrome.menu.permissions')}
129
131
  icon={<SettingsSlidersIcon width="20px" height="20px" />}
130
132
  pathname={pathname}
131
133
  compact={compact}
@@ -137,7 +139,7 @@ export function AdminMenuDrawer(): React.JSX.Element | null {
137
139
  <PreviewToggle compact={compact} />
138
140
  <MenuItem
139
141
  to="/admin/account"
140
- label="Account"
142
+ label={t('chrome.account')}
141
143
  icon={<UserIcon />}
142
144
  pathname={pathname}
143
145
  compact={compact}
@@ -23,6 +23,7 @@
23
23
 
24
24
  import { useEffect, useState } from 'react'
25
25
 
26
+ import { useTranslation } from '@byline/i18n/react'
26
27
  import { EyeClosedIcon, EyeOpenIcon } from '@byline/ui/react'
27
28
  import cx from 'classnames'
28
29
 
@@ -37,6 +38,7 @@ interface PreviewToggleProps {
37
38
  }
38
39
 
39
40
  export function PreviewToggle({ compact }: PreviewToggleProps) {
41
+ const { t } = useTranslation('byline-admin')
40
42
  const [preview, setPreview] = useState<boolean | null>(null)
41
43
  const [busy, setBusy] = useState(false)
42
44
 
@@ -81,7 +83,7 @@ export function PreviewToggle({ compact }: PreviewToggleProps) {
81
83
  // Hide entirely until first read resolves so the label doesn't flicker.
82
84
  if (preview == null) return null
83
85
 
84
- const label = preview ? 'Preview ON' : 'Preview OFF'
86
+ const label = preview ? t('chrome.preview.on') : t('chrome.preview.off')
85
87
  const icon = preview ? (
86
88
  <EyeOpenIcon width="20px" height="20px" />
87
89
  ) : (
@@ -94,10 +96,10 @@ export function PreviewToggle({ compact }: PreviewToggleProps) {
94
96
  type="button"
95
97
  onClick={handleToggle}
96
98
  disabled={busy}
97
- aria-label={preview ? 'Disable preview mode' : 'Enable preview mode'}
98
- title={
99
- preview ? 'Drafts are visible on the public site' : 'Public site shows published only'
99
+ aria-label={
100
+ preview ? t('chrome.preview.disableAriaLabel') : t('chrome.preview.enableAriaLabel')
100
101
  }
102
+ title={preview ? t('chrome.preview.onTitle') : t('chrome.preview.offTitle')}
101
103
  >
102
104
  <span className="icon">{icon}</span>
103
105
  <span className="label">{label}</span>
@@ -16,48 +16,56 @@ import type { ErrorComponentProps, NotFoundRouteProps } from '@tanstack/react-ro
16
16
  import { useRouter } from '@tanstack/react-router'
17
17
 
18
18
  import { BylineError, ErrorCodes } from '@byline/core'
19
+ import type { UseTranslationReturn } from '@byline/i18n/react'
20
+ import { useTranslation } from '@byline/i18n/react'
19
21
  import { Alert, Button, Container, Section } from '@byline/ui/react'
20
22
  import cx from 'classnames'
21
23
 
22
24
  import styles from './route-error.module.css'
23
25
 
24
- const ERROR_TITLES: Record<string, string> = {
25
- [ErrorCodes.NOT_FOUND]: 'Not Found',
26
- [ErrorCodes.VALIDATION]: 'Validation Error',
27
- [ErrorCodes.CONFLICT]: 'Conflict',
28
- [ErrorCodes.INVALID_TRANSITION]: 'Invalid Transition',
29
- [ErrorCodes.PATCH_FAILED]: 'Update Failed',
30
- [ErrorCodes.DATABASE]: 'Database Error',
31
- [ErrorCodes.STORAGE]: 'Storage Error',
32
- [ErrorCodes.UNHANDLED]: 'Unexpected Error',
26
+ // Static map from ErrorCode → translation key. Resolved to a localised
27
+ // string at render time via the `t` function below — module-scope
28
+ // strings would otherwise freeze the English copy in.
29
+ const ERROR_TITLE_KEYS: Record<string, string> = {
30
+ [ErrorCodes.NOT_FOUND]: 'routeError.titles.notFound',
31
+ [ErrorCodes.VALIDATION]: 'routeError.titles.validation',
32
+ [ErrorCodes.CONFLICT]: 'routeError.titles.conflict',
33
+ [ErrorCodes.INVALID_TRANSITION]: 'routeError.titles.invalidTransition',
34
+ [ErrorCodes.PATCH_FAILED]: 'routeError.titles.patchFailed',
35
+ [ErrorCodes.DATABASE]: 'routeError.titles.database',
36
+ [ErrorCodes.STORAGE]: 'routeError.titles.storage',
37
+ [ErrorCodes.UNHANDLED]: 'routeError.titles.unhandled',
33
38
  }
34
39
 
35
- function getErrorTitle(error: unknown): string {
40
+ type Translate = UseTranslationReturn['t']
41
+
42
+ function getErrorTitle(error: unknown, t: Translate): string {
36
43
  if (error instanceof BylineError) {
37
- return ERROR_TITLES[error.code] ?? 'Unexpected Error'
44
+ return t(ERROR_TITLE_KEYS[error.code] ?? 'routeError.titles.unhandled')
38
45
  }
39
- return 'Unexpected Error'
46
+ return t('routeError.titles.unhandled')
40
47
  }
41
48
 
42
- function getErrorMessage(error: unknown): string {
49
+ function getErrorMessage(error: unknown, t: Translate): string {
43
50
  if (error instanceof BylineError) {
44
51
  return error.message
45
52
  }
46
53
  if (error instanceof Error) {
47
54
  return error.message
48
55
  }
49
- return 'An unexpected error occurred. Please try again.'
56
+ return t('routeError.defaultMessage')
50
57
  }
51
58
 
52
59
  export function RouteError({ error, reset }: ErrorComponentProps) {
53
60
  const router = useRouter()
61
+ const { t } = useTranslation('byline-admin')
54
62
 
55
63
  return (
56
64
  <Section className={cx('byline-route-error', styles.section)}>
57
65
  <Container className={cx('byline-route-error-container', styles.container)}>
58
- <Alert intent="danger" icon close={false} title={getErrorTitle(error)}>
66
+ <Alert intent="danger" icon close={false} title={getErrorTitle(error, t)}>
59
67
  <p className={cx('byline-route-error-message', styles.message)}>
60
- {getErrorMessage(error)}
68
+ {getErrorMessage(error, t)}
61
69
  </p>
62
70
  <div className={cx('byline-route-error-actions', styles.actions)}>
63
71
  <Button
@@ -69,10 +77,10 @@ export function RouteError({ error, reset }: ErrorComponentProps) {
69
77
  router.invalidate()
70
78
  }}
71
79
  >
72
- Try again
80
+ {t('common.actions.tryAgain')}
73
81
  </Button>
74
82
  <a href="/" className={cx('byline-route-error-link', styles.link)}>
75
- Go to homepage
83
+ {t('common.actions.goToHomepage')}
76
84
  </a>
77
85
  </div>
78
86
  </Alert>
@@ -82,16 +90,17 @@ export function RouteError({ error, reset }: ErrorComponentProps) {
82
90
  }
83
91
 
84
92
  export function RouteNotFound(_props: NotFoundRouteProps) {
93
+ const { t } = useTranslation('byline-admin')
85
94
  return (
86
95
  <Section className={cx('byline-route-error', styles.section)}>
87
96
  <Container className={cx('byline-route-error-container', styles.container)}>
88
- <Alert intent="warning" icon close={false} title="Page Not Found">
97
+ <Alert intent="warning" icon close={false} title={t('routeError.notFound.title')}>
89
98
  <p className={cx('byline-route-error-message', styles.message)}>
90
- The page you are looking for does not exist or has been moved.
99
+ {t('routeError.notFound.message')}
91
100
  </p>
92
101
  <div className={cx('byline-route-error-actions', styles.actions)}>
93
102
  <a href="/" className={cx('byline-route-error-link', styles.link)}>
94
- Go to homepage
103
+ {t('common.actions.goToHomepage')}
95
104
  </a>
96
105
  </div>
97
106
  </Alert>
@@ -101,17 +110,21 @@ export function RouteNotFound(_props: NotFoundRouteProps) {
101
110
  }
102
111
 
103
112
  /**
104
- * Minimal fallback for when providers may be broken. Used at the root
105
- * route, where wrapping in Container/Section may itself be unsafe.
113
+ * Minimal fallback for when providers may be broken used at the root
114
+ * route where the i18n provider itself may not be mounted. Stays in
115
+ * English on purpose.
106
116
  */
107
117
  export function RootError({ error, reset }: ErrorComponentProps) {
118
+ function rootMessage(err: unknown): string {
119
+ if (err instanceof Error) return err.message
120
+ return 'An unexpected error occurred. Please try again.'
121
+ }
122
+
108
123
  return (
109
124
  <div className={cx('byline-root-error', styles.rootRoot)}>
110
125
  <div className={cx('byline-root-error-inner', styles.rootInner)}>
111
126
  <h1 className={cx('byline-root-error-title', styles.rootTitle)}>Something went wrong</h1>
112
- <p className={cx('byline-root-error-detail', styles.rootDetail)}>
113
- {getErrorMessage(error)}
114
- </p>
127
+ <p className={cx('byline-root-error-detail', styles.rootDetail)}>{rootMessage(error)}</p>
115
128
  <button
116
129
  type="button"
117
130
  onClick={reset}
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Override handles:
5
5
  * .byline-sign-in-page — outer <main>
6
+ * .byline-sign-in-page-bar — top bar carrying the LanguageMenu
6
7
  * .byline-sign-in-page-inner — vertically-centred card container
7
8
  *
8
9
  * The Home link itself lives inside `SignInForm`'s action row — see
@@ -14,10 +15,18 @@
14
15
  display: flex;
15
16
  flex-direction: column;
16
17
  flex: 1 1 0%;
17
- align-items: center;
18
+ align-items: stretch;
18
19
  padding: 1.5rem;
19
20
  }
20
21
 
22
+ .bar,
23
+ :global(.byline-sign-in-page-bar) {
24
+ display: flex;
25
+ justify-content: flex-end;
26
+ align-items: center;
27
+ min-height: 1.75rem;
28
+ }
29
+
21
30
  .inner,
22
31
  :global(.byline-sign-in-page-inner) {
23
32
  display: flex;
@@ -9,35 +9,69 @@
9
9
  import { SignInForm } from '@byline/admin/auth/components/sign-in-form'
10
10
  import { BylineAdminServicesProvider } from '@byline/admin/services'
11
11
  import { getClientConfig } from '@byline/core'
12
+ import type { LocaleCode } from '@byline/i18n'
13
+ import { I18nProvider, LanguageMenu } from '@byline/i18n/react'
12
14
  import cx from 'classnames'
13
15
 
16
+ import { buildLocaleDefinitions } from '../../i18n/locale-definitions.js'
14
17
  import { bylineAdminServices } from '../../integrations/byline-admin-services.js'
18
+ import { setInterfaceLocaleFn } from '../../server-fns/i18n/index.js'
15
19
  import styles from './sign-in-page.module.css'
16
20
 
17
21
  interface SignInPageProps {
18
22
  callbackUrl?: string
23
+ activeLocale: LocaleCode
19
24
  }
20
25
 
21
26
  /**
22
27
  * Sign-in page chrome — rendered outside the authenticated admin
23
28
  * layout (no app bar, breadcrumbs, or menu drawer). Wraps the
24
- * `SignInForm` from `@byline/ui` in the admin services provider so
29
+ * `SignInForm` from `@byline/admin` in the admin services provider so
25
30
  * the form can call `signIn` via the typed contract.
26
31
  *
32
+ * Mounts its own `<I18nProvider>` because the layout-level provider
33
+ * (the one that wraps the authenticated admin) doesn't apply here.
34
+ * `<LanguageMenu>` lights up automatically when two or more interface
35
+ * locales are configured. On change, the menu calls
36
+ * `setInterfaceLocaleFn` which writes the cookie unconditionally and
37
+ * skips the DB write on the pre-auth path (the user has no admin
38
+ * session yet). After sign-in succeeds, the `adminSignIn` server fn
39
+ * reconciles the cookie locale into the user's
40
+ * `admin_users.preferred_locale` so the pre-auth choice becomes
41
+ * sticky across devices from day one.
42
+ *
27
43
  * Threads the configured `serverURL` into the `SignInForm` as `homeUrl` so
28
44
  * the form's action row can render a plain "Home" link beside the submit
29
- * button. After admin sign-out users land back here; the link is what lets
30
- * them get back to the public site without typing the URL.
45
+ * button.
31
46
  */
32
- export function SignInPage({ callbackUrl }: SignInPageProps) {
33
- const { serverURL } = getClientConfig()
47
+ export function SignInPage({ callbackUrl, activeLocale }: SignInPageProps) {
48
+ const { i18n, serverURL } = getClientConfig()
49
+ const localeDefinitions = buildLocaleDefinitions(
50
+ i18n.interface.locales,
51
+ i18n.interface.localeDefinitions
52
+ )
53
+ const handleSetLocale = async (next: LocaleCode) => {
54
+ await setInterfaceLocaleFn({ data: { locale: next } })
55
+ window.location.reload()
56
+ }
34
57
  return (
35
- <BylineAdminServicesProvider services={bylineAdminServices}>
36
- <main className={cx('byline-sign-in-page', styles.main)}>
37
- <div className={cx('byline-sign-in-page-inner', styles.inner)}>
38
- <SignInForm callbackUrl={callbackUrl} homeUrl={serverURL} />
39
- </div>
40
- </main>
41
- </BylineAdminServicesProvider>
58
+ <I18nProvider
59
+ bundle={i18n.translations ?? {}}
60
+ activeLocale={activeLocale}
61
+ defaultLocale={i18n.interface.defaultLocale}
62
+ localeDefinitions={localeDefinitions}
63
+ setLocale={handleSetLocale}
64
+ >
65
+ <BylineAdminServicesProvider services={bylineAdminServices}>
66
+ <main className={cx('byline-sign-in-page', styles.main)}>
67
+ <div className={cx('byline-sign-in-page-bar', styles.bar)}>
68
+ <LanguageMenu />
69
+ </div>
70
+ <div className={cx('byline-sign-in-page-inner', styles.inner)}>
71
+ <SignInForm callbackUrl={callbackUrl} homeUrl={serverURL} />
72
+ </div>
73
+ </main>
74
+ </BylineAdminServicesProvider>
75
+ </I18nProvider>
42
76
  )
43
77
  }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { CollectionDefinition } from '@byline/core'
10
10
  import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
11
+ import { useTranslation } from '@byline/i18n/react'
11
12
  import { Container, Section } from '@byline/ui/react'
12
13
  import cx from 'classnames'
13
14
  import { allExpanded, darkStyles, JsonView } from 'react-json-view-lite'
@@ -41,12 +42,15 @@ export const ApiView = ({
41
42
  defaultContentLocale: string
42
43
  }) => {
43
44
  const { labels, path } = collectionDefinition
45
+ const { t } = useTranslation('byline-admin')
44
46
 
45
47
  return (
46
48
  <Section className={cx('byline-api-section', styles.section)}>
47
49
  <Container className={cx('byline-api-container', styles.container)}>
48
50
  <div className={cx('byline-api-head', styles.head)}>
49
- <h2 className={cx('byline-api-title', styles.title)}>{labels.singular} API</h2>
51
+ <h2 className={cx('byline-api-title', styles.title)}>
52
+ {t('collections.api.title', { label: labels.singular })}
53
+ </h2>
50
54
  <ViewMenu
51
55
  collection={path}
52
56
  documentId={String(initialData.id)}
@@ -8,8 +8,10 @@
8
8
 
9
9
  import { useState } from 'react'
10
10
 
11
+ import { FormRenderer } from '@byline/admin/react'
11
12
  import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
12
- import { Container, FormRenderer, Section, useToastManager } from '@byline/ui/react'
13
+ import { useTranslation } from '@byline/i18n/react'
14
+ import { Container, Section, useToastManager } from '@byline/ui/react'
13
15
 
14
16
  import { createCollectionDocument } from '../../server-fns/collections/index.js'
15
17
  import { useNavigate } from '../chrome/loose-router.js'
@@ -31,6 +33,7 @@ export const CreateView = ({
31
33
  initialData?: Record<string, any>
32
34
  }) => {
33
35
  const toastManager = useToastManager()
36
+ const { t } = useTranslation('byline-admin')
34
37
  const [_createState, setCreateState] = useState<CreateState>({
35
38
  status: 'idle',
36
39
  message: '',
@@ -62,9 +65,12 @@ export const CreateView = ({
62
65
  } catch (err) {
63
66
  console.error(err)
64
67
 
68
+ const description = t('collections.create.errorToastDescription', {
69
+ label: labels.singular.toLowerCase(),
70
+ })
65
71
  toastManager.add({
66
- title: `${labels.singular} Creation`,
67
- description: `An error occurred while creating ${labels.singular.toLowerCase()}`,
72
+ title: t('collections.create.errorToastTitle', { label: labels.singular }),
73
+ description,
68
74
  data: {
69
75
  intent: 'danger',
70
76
  iconType: 'danger',
@@ -75,7 +81,7 @@ export const CreateView = ({
75
81
 
76
82
  setCreateState({
77
83
  status: 'failed',
78
- message: `An error occurred while creating ${labels.singular.toLowerCase()}`,
84
+ message: description,
79
85
  })
80
86
  }
81
87
  }