@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
@@ -35,6 +35,7 @@ import {
35
35
  } from '../server-fns/admin-users/index.js'
36
36
  import { adminSignIn } from '../server-fns/auth/index.js'
37
37
  import { getCollectionDocumentVersion as serverGetCollectionDocumentVersion } from '../server-fns/collections/get.js'
38
+ import { setInterfaceLocaleFn } from '../server-fns/i18n/index.js'
38
39
 
39
40
  /**
40
41
  * Diff helper adapter — the contract uses positional args; the underlying
@@ -66,6 +67,7 @@ export const bylineAdminServices: BylineAdminServices = {
66
67
  // Account self-service
67
68
  updateAccount,
68
69
  changeAccountPassword,
70
+ setInterfaceLocale: setInterfaceLocaleFn,
69
71
 
70
72
  // Admin user writes
71
73
  createAdminUser,
@@ -9,14 +9,18 @@
9
9
  /**
10
10
  * Host-side adapters that bind the webapp's TanStack Start server functions
11
11
  * to the framework-neutral `BylineFieldServices` contract consumed by
12
- * `@byline/ui` field/form components.
12
+ * `@byline/admin` field/form components.
13
13
  *
14
14
  * Wired into the admin route once via `<BylineFieldServicesProvider>`. A
15
15
  * future Next.js host would ship its own adapter file and Provider; the
16
- * @byline/ui surface is unchanged.
16
+ * @byline/admin surface is unchanged.
17
17
  */
18
18
 
19
- import type { BylineFieldServices, GetCollectionDocumentsFn, UploadFieldFn } from '@byline/ui/react'
19
+ import type {
20
+ BylineFieldServices,
21
+ GetCollectionDocumentsFn,
22
+ UploadFieldFn,
23
+ } from '@byline/admin/react'
20
24
 
21
25
  import { getCollectionDocuments as serverGetCollectionDocuments } from '../server-fns/collections/list.js'
22
26
  import { uploadField as serverUploadField } from '../server-fns/collections/upload.js'
@@ -9,6 +9,7 @@
9
9
  import { createFileRoute } from '@tanstack/react-router'
10
10
 
11
11
  import { AccountSelfContainer } from '@byline/admin/admin-account/components/container'
12
+ import { useTranslation } from '@byline/i18n/react'
12
13
  import { Container, Section } from '@byline/ui/react'
13
14
 
14
15
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
@@ -37,18 +38,19 @@ export function createAdminAccountRoute(path: string) {
37
38
  },
38
39
  component: function AdminAccountComponent() {
39
40
  const { account } = Route.useLoaderData() as { account: AccountResponse }
41
+ const { t } = useTranslation('byline-admin')
40
42
  return (
41
43
  <>
42
44
  <BreadcrumbsClient
43
45
  breadcrumbs={[
44
- { label: 'Dashboard', href: '/admin' },
45
- { label: 'Account', href: '/admin/account' },
46
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
47
+ { label: t('chrome.account'), href: '/admin/account' },
46
48
  ]}
47
49
  />
48
50
  <Section className="pb-2">
49
- <Container>
51
+ <Container className="mb-2">
50
52
  <h1 className="mb-2">{displayNameFor(account)}</h1>
51
- <p className="muted">Manage your own profile and password.</p>
53
+ <p className="muted">{t('account.intro')}</p>
52
54
  </Container>
53
55
  </Section>
54
56
  <Section>
@@ -9,6 +9,7 @@
9
9
  import { createFileRoute } from '@tanstack/react-router'
10
10
 
11
11
  import { getClientConfig } from '@byline/core'
12
+ import { useTranslation } from '@byline/i18n/react'
12
13
 
13
14
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
14
15
  import { AdminDashboard } from '../admin-shell/chrome/dashboard.js'
@@ -39,9 +40,12 @@ export function createAdminDashboardRoute(path: string) {
39
40
  const { statsMap } = Route.useLoaderData() as {
40
41
  statsMap: Record<string, CollectionStatusCount[]>
41
42
  }
43
+ const { t } = useTranslation('byline-admin')
42
44
  return (
43
45
  <>
44
- <BreadcrumbsClient breadcrumbs={[{ label: 'Dashboard', href: '/admin' }]} />
46
+ <BreadcrumbsClient
47
+ breadcrumbs={[{ label: t('chrome.menu.dashboard'), href: '/admin' }]}
48
+ />
45
49
  <AdminDashboard statsMap={statsMap} />
46
50
  </>
47
51
  )
@@ -21,8 +21,11 @@
21
21
 
22
22
  import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
23
23
 
24
+ import { BylineFieldServicesProvider } from '@byline/admin/react'
24
25
  import { BylineAdminServicesProvider } from '@byline/admin/services'
25
- import { BylineFieldServicesProvider } from '@byline/ui/react'
26
+ import { getClientConfig } from '@byline/core'
27
+ import type { LocaleCode } from '@byline/i18n'
28
+ import { I18nProvider } from '@byline/i18n/react'
26
29
  import cx from 'classnames'
27
30
 
28
31
  import { AdminAppBar } from '../admin-shell/chrome/admin-app-bar.js'
@@ -33,10 +36,12 @@ import { AdminMenuDrawer } from '../admin-shell/chrome/menu-drawer.js'
33
36
  import { AdminMenuProvider } from '../admin-shell/chrome/menu-provider.js'
34
37
  import { RouteError, RouteNotFound } from '../admin-shell/chrome/route-error.js'
35
38
  import { RouteProgressBar } from '../admin-shell/chrome/route-progress-bar.js'
39
+ import { buildLocaleDefinitions } from '../i18n/locale-definitions.js'
36
40
  import { bylineAdminServices } from '../integrations/byline-admin-services.js'
37
41
  import { BylineAiAdminProvider } from '../integrations/byline-ai.js'
38
42
  import { bylineFieldServices } from '../integrations/byline-field-services.js'
39
43
  import { getCurrentAdminUser } from '../server-fns/auth/index.js'
44
+ import { getActiveLocaleFn, setInterfaceLocaleFn } from '../server-fns/i18n/index.js'
40
45
 
41
46
  interface AdminLayoutOpts {
42
47
  /** Path users are redirected to when unauthenticated. Defaults to `/sign-in`. */
@@ -51,7 +56,14 @@ export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {})
51
56
  beforeLoad: async ({ location }: { location: { href: string } }) => {
52
57
  try {
53
58
  const user = await getCurrentAdminUser()
54
- return { user }
59
+ // Resolve the active interface locale once on the server so SSR
60
+ // and the hydrated client render the same translations and
61
+ // there's no locale flicker. Going through the server fn (rather
62
+ // than calling `resolveRequestLocale` directly) keeps
63
+ // `@tanstack/react-start/server` out of the client bundle — the
64
+ // same pattern `getCurrentAdminUser` uses for `getAdminRequestContext`.
65
+ const activeLocale = await getActiveLocaleFn()
66
+ return { user, activeLocale }
55
67
  } catch {
56
68
  // `getCurrentAdminUser` (via `getAdminRequestContext`) throws
57
69
  // `ERR_UNAUTHENTICATED` or a related auth error when no valid
@@ -64,27 +76,48 @@ export function createAdminLayoutRoute(path: string, opts: AdminLayoutOpts = {})
64
76
  }
65
77
  },
66
78
  component: function AdminLayoutComponent() {
67
- const { user } = Route.useRouteContext() as {
79
+ const { user, activeLocale } = Route.useRouteContext() as {
68
80
  user: Awaited<ReturnType<typeof getCurrentAdminUser>>
81
+ activeLocale: LocaleCode
82
+ }
83
+ const { i18n } = getClientConfig()
84
+ const localeDefinitions = buildLocaleDefinitions(
85
+ i18n.interface.locales,
86
+ i18n.interface.localeDefinitions
87
+ )
88
+ // Cookie + DB write happen in setInterfaceLocaleFn; full reload
89
+ // re-runs beforeLoad so the provider re-renders with the new
90
+ // bundle/locale (no in-place bundle swap needed for PR 1's scope).
91
+ const handleSetLocale = async (next: LocaleCode) => {
92
+ await setInterfaceLocaleFn({ data: { locale: next } })
93
+ window.location.reload()
69
94
  }
70
95
  return (
71
- <BylineAdminServicesProvider services={bylineAdminServices}>
72
- <BylineFieldServicesProvider services={bylineFieldServices}>
73
- <BylineAiAdminProvider>
74
- <AdminMenuProvider>
75
- <RouteProgressBar />
76
- <AdminAppBar user={user} />
77
- <main className={cx('byline-admin-layout-main', layoutStyles.main)}>
78
- <DrawerToggle />
79
- <AdminMenuDrawer />
80
- <Content>
81
- <Outlet />
82
- </Content>
83
- </main>
84
- </AdminMenuProvider>
85
- </BylineAiAdminProvider>
86
- </BylineFieldServicesProvider>
87
- </BylineAdminServicesProvider>
96
+ <I18nProvider
97
+ bundle={i18n.translations ?? {}}
98
+ activeLocale={activeLocale}
99
+ defaultLocale={i18n.interface.defaultLocale}
100
+ localeDefinitions={localeDefinitions}
101
+ setLocale={handleSetLocale}
102
+ >
103
+ <BylineAdminServicesProvider services={bylineAdminServices}>
104
+ <BylineFieldServicesProvider services={bylineFieldServices}>
105
+ <BylineAiAdminProvider>
106
+ <AdminMenuProvider>
107
+ <RouteProgressBar />
108
+ <AdminAppBar user={user} />
109
+ <main className={cx('byline-admin-layout-main', layoutStyles.main)}>
110
+ <DrawerToggle />
111
+ <AdminMenuDrawer />
112
+ <Content>
113
+ <Outlet />
114
+ </Content>
115
+ </main>
116
+ </AdminMenuProvider>
117
+ </BylineAiAdminProvider>
118
+ </BylineFieldServicesProvider>
119
+ </BylineAdminServicesProvider>
120
+ </I18nProvider>
88
121
  )
89
122
  },
90
123
  errorComponent: RouteError,
@@ -9,6 +9,7 @@
9
9
  import { createFileRoute } from '@tanstack/react-router'
10
10
 
11
11
  import { AbilitiesInspector } from '@byline/admin/admin-permissions/components/inspector'
12
+ import { useTranslation } from '@byline/i18n/react'
12
13
 
13
14
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
14
15
  import {
@@ -25,12 +26,13 @@ export function createAdminPermissionsRoute(path: string) {
25
26
  },
26
27
  component: function AdminPermissionsComponent() {
27
28
  const { data } = Route.useLoaderData() as { data: ListRegisteredAbilitiesResponse }
29
+ const { t } = useTranslation('byline-admin')
28
30
  return (
29
31
  <>
30
32
  <BreadcrumbsClient
31
33
  breadcrumbs={[
32
- { label: 'Dashboard', href: '/admin' },
33
- { label: 'Permissions', href: '/admin/permissions' },
34
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
35
+ { label: t('chrome.menu.permissions'), href: '/admin/permissions' },
34
36
  ]}
35
37
  />
36
38
  <AbilitiesInspector data={data} />
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { createFileRoute, notFound } from '@tanstack/react-router'
10
10
 
11
+ import { useTranslation } from '@byline/i18n/react'
11
12
  import { Container, Section } from '@byline/ui/react'
12
13
 
13
14
  import { RoleContainer } from '../admin-shell/admin-roles/container.js'
@@ -56,13 +57,14 @@ export function createAdminRoleEditRoute(path: string) {
56
57
  },
57
58
  component: function AdminRoleEditComponent() {
58
59
  const { role, registered, initialAbilities } = Route.useLoaderData() as LoaderData
60
+ const { t } = useTranslation('byline-admin')
59
61
  return (
60
62
  <>
61
63
  <BreadcrumbsClient
62
64
  breadcrumbs={[
63
- { label: 'Dashboard', href: '/admin' },
64
- { label: 'Admin Roles', href: '/admin/roles' },
65
- { label: 'Role', href: `/admin/roles/${role.id}` },
65
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
66
+ { label: t('chrome.menu.adminRoles'), href: '/admin/roles' },
67
+ { label: t('adminRoles.breadcrumbDetail'), href: `/admin/roles/${role.id}` },
66
68
  ]}
67
69
  />
68
70
  <Section className="py-5 pb-2">
@@ -8,6 +8,8 @@
8
8
 
9
9
  import { createFileRoute } from '@tanstack/react-router'
10
10
 
11
+ import { useTranslation } from '@byline/i18n/react'
12
+
11
13
  import { AdminRolesListView } from '../admin-shell/admin-roles/list.js'
12
14
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
13
15
  import { type AdminRoleListResponse, listAdminRoles } from '../server-fns/admin-roles/index.js'
@@ -21,12 +23,13 @@ export function createAdminRolesListRoute(path: string) {
21
23
  },
22
24
  component: function AdminRolesListComponent() {
23
25
  const { data } = Route.useLoaderData() as { data: AdminRoleListResponse }
26
+ const { t } = useTranslation('byline-admin')
24
27
  return (
25
28
  <>
26
29
  <BreadcrumbsClient
27
30
  breadcrumbs={[
28
- { label: 'Dashboard', href: '/admin' },
29
- { label: 'Admin Roles', href: '/admin/roles' },
31
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
32
+ { label: t('chrome.menu.adminRoles'), href: '/admin/roles' },
30
33
  ]}
31
34
  />
32
35
  <AdminRolesListView data={data} />
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { createFileRoute, notFound } from '@tanstack/react-router'
10
10
 
11
+ import { useTranslation } from '@byline/i18n/react'
11
12
  import { Container, Section } from '@byline/ui/react'
12
13
 
13
14
  import { AccountContainer } from '../admin-shell/admin-users/container.js'
@@ -73,13 +74,14 @@ export function createAdminUserEditRoute(path: string) {
73
74
  },
74
75
  component: function AdminUserEditComponent() {
75
76
  const { user, allRoles, initialUserRoles } = Route.useLoaderData() as LoaderData
77
+ const { t } = useTranslation('byline-admin')
76
78
  return (
77
79
  <>
78
80
  <BreadcrumbsClient
79
81
  breadcrumbs={[
80
- { label: 'Dashboard', href: '/admin' },
81
- { label: 'Admin Users', href: '/admin/users' },
82
- { label: 'User', href: `/admin/users/${user.id}` },
82
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
83
+ { label: t('chrome.menu.adminUsers'), href: '/admin/users' },
84
+ { label: t('adminUsers.breadcrumbDetail'), href: `/admin/users/${user.id}` },
83
85
  ]}
84
86
  />
85
87
  <Section className="py-5 pb-2">
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { createFileRoute } from '@tanstack/react-router'
10
10
 
11
+ import { useTranslation } from '@byline/i18n/react'
11
12
  import { z } from 'zod'
12
13
 
13
14
  import { AdminUsersListView } from '../admin-shell/admin-users/list.js'
@@ -66,12 +67,13 @@ export function createAdminUsersListRoute(path: string) {
66
67
  },
67
68
  component: function AdminUsersListComponent() {
68
69
  const { data } = Route.useLoaderData() as { data: AdminUserListResponse }
70
+ const { t } = useTranslation('byline-admin')
69
71
  return (
70
72
  <>
71
73
  <BreadcrumbsClient
72
74
  breadcrumbs={[
73
- { label: 'Dashboard', href: '/admin' },
74
- { label: 'Admin Users', href: '/admin/users' },
75
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
76
+ { label: t('chrome.menu.adminUsers'), href: '/admin/users' },
75
77
  ]}
76
78
  />
77
79
  <AdminUsersListView data={data} />
@@ -10,6 +10,7 @@ import { createFileRoute, notFound } from '@tanstack/react-router'
10
10
 
11
11
  import type { CollectionDefinition } from '@byline/core'
12
12
  import { getCollectionDefinition } from '@byline/core'
13
+ import { useTranslation } from '@byline/i18n/react'
13
14
  import { z } from 'zod'
14
15
 
15
16
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
@@ -77,19 +78,20 @@ export function createCollectionApiRoute(path: string, opts: CollectionApiOpts)
77
78
  const { collection, id } = Route.useParams() as { collection: string; id: string }
78
79
  const { locale, depth } = Route.useSearch() as z.infer<typeof searchSchema>
79
80
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
81
+ const { t } = useTranslation('byline-admin')
80
82
 
81
83
  return (
82
84
  <>
83
85
  <BreadcrumbsClient
84
86
  breadcrumbs={[
85
- { label: 'Dashboard', href: `/admin` },
87
+ { label: t('chrome.menu.dashboard'), href: `/admin` },
86
88
  { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
87
89
  {
88
- label: 'Edit',
90
+ label: t('common.actions.edit'),
89
91
  href: `/admin/collections/${collection}/${id}`,
90
92
  },
91
93
  {
92
- label: 'API',
94
+ label: t('collections.viewMenu.apiButton'),
93
95
  href: `/admin/collections/${collection}/${id}/api`,
94
96
  },
95
97
  ]}
@@ -14,6 +14,7 @@ import {
14
14
  getCollectionAdminConfig,
15
15
  getCollectionDefinition,
16
16
  } from '@byline/core'
17
+ import { useTranslation } from '@byline/i18n/react'
17
18
 
18
19
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
19
20
  import { CreateView } from '../admin-shell/collections/create.js'
@@ -43,13 +44,17 @@ export function createCollectionCreateRoute(path: string) {
43
44
  const { initialData } = Route.useLoaderData() as { initialData: any }
44
45
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
45
46
  const adminConfig = getCollectionAdminConfig(collection)
47
+ const { t } = useTranslation('byline-admin')
46
48
  return (
47
49
  <>
48
50
  <BreadcrumbsClient
49
51
  breadcrumbs={[
50
- { label: 'Dashboard', href: '/admin' },
52
+ { label: t('chrome.menu.dashboard'), href: '/admin' },
51
53
  { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
52
- { label: 'Create', href: `/admin/collections/${collection}/create` },
54
+ {
55
+ label: t('collections.breadcrumbs.create'),
56
+ href: `/admin/collections/${collection}/create`,
57
+ },
53
58
  ]}
54
59
  />
55
60
  <CreateView
@@ -10,6 +10,7 @@ import { createFileRoute, notFound } from '@tanstack/react-router'
10
10
 
11
11
  import type { CollectionDefinition } from '@byline/core'
12
12
  import { getCollectionAdminConfig, getCollectionDefinition } from '@byline/core'
13
+ import { useTranslation } from '@byline/i18n/react'
13
14
  import { z } from 'zod'
14
15
 
15
16
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
@@ -72,15 +73,16 @@ export function createCollectionEditRoute(path: string, opts: CollectionEditOpts
72
73
  const { locale } = Route.useSearch() as z.infer<typeof searchSchema>
73
74
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
74
75
  const adminConfig = getCollectionAdminConfig(collection)
76
+ const { t } = useTranslation('byline-admin')
75
77
 
76
78
  return (
77
79
  <>
78
80
  <BreadcrumbsClient
79
81
  breadcrumbs={[
80
- { label: 'Dashboard', href: `/admin` },
82
+ { label: t('chrome.menu.dashboard'), href: `/admin` },
81
83
  { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
82
84
  {
83
- label: 'Edit',
85
+ label: t('common.actions.edit'),
84
86
  href: `/admin/collections/${collection}/${id}`,
85
87
  },
86
88
  ]}
@@ -14,6 +14,7 @@ import {
14
14
  getCollectionDefinition,
15
15
  getWorkflowStatuses,
16
16
  } from '@byline/core'
17
+ import { useTranslation } from '@byline/i18n/react'
17
18
  import { z } from 'zod'
18
19
 
19
20
  import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
@@ -95,19 +96,20 @@ export function createCollectionHistoryRoute(path: string, opts: CollectionHisto
95
96
  const { collection } = Route.useParams() as { collection: string; id: string }
96
97
  const collectionDef = getCollectionDefinition(collection) as CollectionDefinition
97
98
  const adminConfig = getCollectionAdminConfig(collection)
99
+ const { t } = useTranslation('byline-admin')
98
100
 
99
101
  return (
100
102
  <>
101
103
  <BreadcrumbsClient
102
104
  breadcrumbs={[
103
- { label: 'Dashboard', href: `/admin` },
105
+ { label: t('chrome.menu.dashboard'), href: `/admin` },
104
106
  { label: collectionDef.labels.plural, href: `/admin/collections/${collection}` },
105
107
  {
106
- label: 'Edit',
108
+ label: t('common.actions.edit'),
107
109
  href: `/admin/collections/${collection}/${Route.useParams().id}`,
108
110
  },
109
111
  {
110
- label: 'History',
112
+ label: t('collections.breadcrumbs.history'),
111
113
  href: `/admin/collections/${collection}/${Route.useParams().id}/history`,
112
114
  },
113
115
  ]}
@@ -15,6 +15,7 @@ import {
15
15
  getCollectionDefinition,
16
16
  getWorkflowStatuses,
17
17
  } from '@byline/core'
18
+ import { useTranslation } from '@byline/i18n/react'
18
19
  import { useToastManager } from '@byline/ui/react'
19
20
  import { z } from 'zod'
20
21
 
@@ -97,6 +98,7 @@ export function createCollectionListRoute(path: string) {
97
98
  },
98
99
  component: function CollectionListComponent() {
99
100
  const toastManager = useToastManager()
101
+ const { t } = useTranslation('byline-admin')
100
102
  const data = Route.useLoaderData()
101
103
  const { collection } = Route.useParams() as { collection: string }
102
104
  const search = Route.useSearch() as z.infer<typeof searchSchema>
@@ -124,8 +126,10 @@ export function createCollectionListRoute(path: string) {
124
126
  createdToastFiredRef.current = true
125
127
 
126
128
  toastManager.add({
127
- title: `${collectionDef.labels.singular} Created`,
128
- description: `Successfully created ${collectionDef.labels.singular.toLowerCase()}`,
129
+ title: t('collections.list.createdToastTitle', { label: collectionDef.labels.singular }),
130
+ description: t('collections.list.createdToastDescription', {
131
+ label: collectionDef.labels.singular.toLowerCase(),
132
+ }),
129
133
  data: {
130
134
  intent: 'success',
131
135
  iconType: 'success',
@@ -138,13 +142,7 @@ export function createCollectionListRoute(path: string) {
138
142
  search: (prev: Record<string, unknown>) => ({ ...prev, action: undefined }),
139
143
  replace: true,
140
144
  })
141
- }, [
142
- search.action,
143
- navigate,
144
- toastManager.add,
145
- collectionDef.labels.singular.toLowerCase,
146
- collectionDef.labels.singular,
147
- ])
145
+ }, [search.action, navigate, toastManager.add, collectionDef.labels.singular, t])
148
146
 
149
147
  const CustomListView = adminConfig?.listView
150
148
 
@@ -152,7 +150,7 @@ export function createCollectionListRoute(path: string) {
152
150
  <>
153
151
  <BreadcrumbsClient
154
152
  breadcrumbs={[
155
- { label: 'Dashboard', href: `/admin` },
153
+ { label: t('chrome.menu.dashboard'), href: `/admin` },
156
154
  {
157
155
  label: data.included.collection.labels.plural,
158
156
  href: `/admin/collections/${collection}`,
@@ -17,7 +17,10 @@
17
17
 
18
18
  import { createFileRoute } from '@tanstack/react-router'
19
19
 
20
+ import type { LocaleCode } from '@byline/i18n'
21
+
20
22
  import { SignInPage } from '../admin-shell/chrome/sign-in-page.js'
23
+ import { getActiveLocaleFn } from '../server-fns/i18n/index.js'
21
24
 
22
25
  interface SignInSearch {
23
26
  callbackUrl?: string
@@ -30,9 +33,19 @@ export function createSignInRoute(path: string) {
30
33
  const callbackUrl = typeof search.callbackUrl === 'string' ? search.callbackUrl : undefined
31
34
  return { callbackUrl }
32
35
  },
36
+ beforeLoad: async () => {
37
+ // Resolve the active locale on the server before render so the
38
+ // sign-in page hydrates in the user's chosen language. The
39
+ // resolver works pre-auth — `readPreferredLocaleFromActor()`
40
+ // returns null when there's no admin session and the cascade
41
+ // falls through to cookie → Accept-Language → defaultLocale.
42
+ const activeLocale = await getActiveLocaleFn()
43
+ return { activeLocale }
44
+ },
33
45
  component: function SignInRouteComponent() {
34
46
  const { callbackUrl } = Route.useSearch() as SignInSearch
35
- return <SignInPage callbackUrl={callbackUrl} />
47
+ const { activeLocale } = Route.useRouteContext() as { activeLocale: LocaleCode }
48
+ return <SignInPage callbackUrl={callbackUrl} activeLocale={activeLocale} />
36
49
  },
37
50
  })
38
51
  return Route
@@ -23,6 +23,8 @@ import { getRequestHeader } from '@tanstack/react-start/server'
23
23
  import { getServerConfig } from '@byline/core'
24
24
 
25
25
  import { setSessionCookies } from '../../auth/auth-cookies.js'
26
+ import { readAdminLocaleCookie } from '../../i18n/locale-cookie.js'
27
+ import { bylineCore } from '../../integrations/byline-core.js'
26
28
 
27
29
  export interface SignInInput {
28
30
  email: string
@@ -67,5 +69,48 @@ export const adminSignIn = createServerFn({ method: 'POST' })
67
69
 
68
70
  setSessionCookies(result)
69
71
 
72
+ // Reconcile the byline_admin_lng cookie against the freshly-signed-in
73
+ // user's stored preferred_locale. If the cookie carries a permitted
74
+ // locale and it differs from what the user has stored (including the
75
+ // "null preferred_locale" case for brand-new users), update the
76
+ // column so the pre-auth locale choice becomes sticky across devices
77
+ // from day one. Pure best-effort — any error short-circuits and the
78
+ // sign-in still succeeds.
79
+ try {
80
+ await reconcileLocaleAfterSignIn(result.actor.id)
81
+ } catch {
82
+ // Swallow — locale sync is not load-bearing for the sign-in flow.
83
+ }
84
+
70
85
  return { userId: result.actor.id }
71
86
  })
87
+
88
+ /**
89
+ * Apply the `byline_admin_lng` cookie to `admin_users.preferred_locale`
90
+ * when the two diverge after sign-in. No-op when:
91
+ *
92
+ * - The cookie is unset (the cascade falls through to the existing
93
+ * column / Accept-Language / default anyway).
94
+ * - The cookie carries a locale outside `i18n.interface.locales`
95
+ * (stale value pointing at a removed locale — let the resolver
96
+ * fall through cleanly).
97
+ * - No admin store is configured (headless tooling paths).
98
+ * - The stored value already matches the cookie.
99
+ */
100
+ async function reconcileLocaleAfterSignIn(adminUserId: string): Promise<void> {
101
+ const cookieLocale = readAdminLocaleCookie()
102
+ if (cookieLocale == null) return
103
+
104
+ const core = bylineCore()
105
+ const locales = core.config.i18n.interface.locales
106
+ if (!locales.includes(cookieLocale)) return
107
+
108
+ const adminStore = core.adminStore
109
+ if (adminStore == null) return
110
+
111
+ const row = await adminStore.adminUsers.getById(adminUserId)
112
+ if (!row) return
113
+ if (row.preferred_locale === cookieLocale) return
114
+
115
+ await adminStore.adminUsers.setPreferredLocale(adminUserId, cookieLocale)
116
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Resolve the active interface locale for the current request as a
11
+ * server function. Wraps `resolveRequestLocale` so route `beforeLoad`
12
+ * callers in client-touching files don't transitively pull the
13
+ * `@tanstack/react-start/server` import (and the
14
+ * `@tanstack/start-server-core` graph behind it) into the client
15
+ * bundle. Same pattern as `getCurrentAdminUser`.
16
+ */
17
+
18
+ import { createServerFn } from '@tanstack/react-start'
19
+
20
+ import { resolveRequestLocale } from '../../i18n/resolve-locale.js'
21
+
22
+ export const getActiveLocaleFn = createServerFn({ method: 'GET' }).handler(
23
+ async (): Promise<string> => {
24
+ return resolveRequestLocale()
25
+ }
26
+ )
@@ -0,0 +1,11 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ export { getActiveLocaleFn } from './get-active-locale.js'
10
+ export { setInterfaceLocaleFn } from './set-locale.js'
11
+ export type { SetInterfaceLocaleInput, SetInterfaceLocaleResult } from './set-locale.js'