@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
@@ -0,0 +1,17 @@
1
+ import { getCollectionDefinition } from "@byline/core";
2
+ function resolvePreviewUrl(doc, collectionPath, adminConfig, locale) {
3
+ if (adminConfig?.preview) return adminConfig.preview.url(doc, {
4
+ locale
5
+ });
6
+ const definition = getCollectionDefinition(collectionPath);
7
+ if (definition?.buildDocumentPath != null) try {
8
+ const built = definition.buildDocumentPath(doc, {
9
+ collectionPath
10
+ });
11
+ if ('string' == typeof built) return built;
12
+ if (null === built) return null;
13
+ } catch {}
14
+ if (!doc.path) return null;
15
+ return `/${collectionPath}/${doc.path}`;
16
+ }
17
+ export { resolvePreviewUrl };
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { useRouter } from "@tanstack/react-router";
5
+ import { useTranslation } from "@byline/i18n/react";
5
6
  import { Alert, Button, LoaderEllipsis, Modal } from "@byline/ui/react";
6
7
  import classnames from "classnames";
7
8
  import { restoreDocumentVersion } from "../../server-fns/collections/index.js";
@@ -10,6 +11,7 @@ import restore_version_modal_module from "./restore-version-modal.module.js";
10
11
  function RestoreVersionModal({ collection, documentId, versionId, versionLabel, versionNumber, onClose }) {
11
12
  const navigate = useNavigate();
12
13
  const router = useRouter();
14
+ const { t } = useTranslation('byline-admin');
13
15
  const [error, setError] = useState(null);
14
16
  const [pending, setPending] = useState(false);
15
17
  async function handleRestore() {
@@ -35,9 +37,9 @@ function RestoreVersionModal({ collection, documentId, versionId, versionLabel,
35
37
  });
36
38
  } catch (err) {
37
39
  const code = getErrorCode(err);
38
- if ('ERR_INVALID_TRANSITION' === code) setError('This version is already the current version of the document.');
39
- else if ('ERR_NOT_FOUND' === code) setError('The selected version could not be found. The history may be out of date.');
40
- else 'ERR_FORBIDDEN' === code || 'ERR_UNAUTHENTICATED' === code ? setError('You do not have permission to restore versions for this collection.') : setError('Could not restore this version. Please try again.');
40
+ if ('ERR_INVALID_TRANSITION' === code) setError(t('collections.restore.errors.alreadyCurrent'));
41
+ else if ('ERR_NOT_FOUND' === code) setError(t('collections.restore.errors.notFound'));
42
+ else 'ERR_FORBIDDEN' === code || 'ERR_UNAUTHENTICATED' === code ? setError(t('collections.restore.errors.forbidden')) : setError(t('collections.restore.errors.fallback'));
41
43
  setPending(false);
42
44
  }
43
45
  }
@@ -57,7 +59,7 @@ function RestoreVersionModal({ collection, documentId, versionId, versionLabel,
57
59
  children: [
58
60
  /*#__PURE__*/ jsx("span", {
59
61
  className: "muted",
60
- children: "Version:"
62
+ children: t('collections.restore.versionLabel')
61
63
  }),
62
64
  " ",
63
65
  versionNumber
@@ -68,20 +70,17 @@ function RestoreVersionModal({ collection, documentId, versionId, versionLabel,
68
70
  children: [
69
71
  /*#__PURE__*/ jsx("span", {
70
72
  className: "muted",
71
- children: "Created:"
73
+ children: t('collections.restore.createdLabel')
72
74
  }),
73
75
  " ",
74
76
  versionLabel
75
77
  ]
76
78
  }),
77
- /*#__PURE__*/ jsxs("p", {
79
+ /*#__PURE__*/ jsx("p", {
78
80
  className: classnames('byline-coll-restore-warning', restore_version_modal_module.warning),
79
- children: [
80
- "This will create a new draft version of this document with the content from version",
81
- ' ',
82
- versionNumber,
83
- ", and that draft will become the current version. The existing versions (including any published version) are preserved in history. The restored draft will need to be published through the normal workflow."
84
- ]
81
+ children: t('collections.restore.warning', {
82
+ version: versionNumber
83
+ })
85
84
  })
86
85
  ]
87
86
  }),
@@ -105,7 +104,7 @@ function RestoreVersionModal({ collection, documentId, versionId, versionLabel,
105
104
  onClick: onClose,
106
105
  disabled: pending,
107
106
  className: classnames('byline-coll-restore-button', restore_version_modal_module.button),
108
- children: "Cancel"
107
+ children: t('common.actions.cancel')
109
108
  }),
110
109
  /*#__PURE__*/ jsx(Button, {
111
110
  size: "sm",
@@ -118,7 +117,7 @@ function RestoreVersionModal({ collection, documentId, versionId, versionLabel,
118
117
  className: classnames('byline-coll-restore-button', restore_version_modal_module.button),
119
118
  children: true === pending ? /*#__PURE__*/ jsx(LoaderEllipsis, {
120
119
  size: 42
121
- }) : 'Restore as Draft'
120
+ }) : t('collections.restore.confirmButton')
122
121
  })
123
122
  ]
124
123
  })
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { UseNavigationGuard } from '@byline/ui/react';
8
+ import type { UseNavigationGuard } from '@byline/admin/react';
9
9
  /**
10
10
  * Navigation guard backed by TanStack Router's `useBlocker`.
11
11
  *
@@ -1,5 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
+ import { useTranslation } from "@byline/i18n/react";
3
4
  import { Button, HistoryIcon, IconButton, Label, Select } from "@byline/ui/react";
4
5
  import classnames from "classnames";
5
6
  import { useNavigate } from "../chrome/loose-router.js";
@@ -7,6 +8,7 @@ import { PreviewLink } from "./preview-link.js";
7
8
  import view_menu_module from "./view-menu.module.js";
8
9
  const ViewMenu = ({ collection, documentId, activeView, locale, depth, contentLocales, defaultContentLocale, adminConfig, doc })=>{
9
10
  const navigate = useNavigate();
11
+ const { t } = useTranslation('byline-admin');
10
12
  useEffect(()=>{
11
13
  if ('edit' === activeView && 'all' === locale) navigate({
12
14
  to: '/admin/collections/$collection/$id',
@@ -80,7 +82,7 @@ const ViewMenu = ({ collection, documentId, activeView, locale, depth, contentLo
80
82
  className: classnames('muted byline-view-menu-label', view_menu_module.label),
81
83
  id: "contentLocaleLabel",
82
84
  htmlFor: "contentLocale",
83
- label: "Content Locale:"
85
+ label: t('collections.viewMenu.contentLocaleLabel')
84
86
  }),
85
87
  /*#__PURE__*/ jsx(Select, {
86
88
  name: "contentLocale",
@@ -93,7 +95,7 @@ const ViewMenu = ({ collection, documentId, activeView, locale, depth, contentLo
93
95
  ...'edit' !== activeView ? [
94
96
  {
95
97
  value: 'all',
96
- label: 'All'
98
+ label: t('collections.viewMenu.localeAll')
97
99
  }
98
100
  ] : [],
99
101
  ...contentLocales.map((loc)=>({
@@ -109,7 +111,7 @@ const ViewMenu = ({ collection, documentId, activeView, locale, depth, contentLo
109
111
  className: classnames('muted byline-view-menu-label', view_menu_module.label),
110
112
  id: "populateDepthLabel",
111
113
  htmlFor: "populateDepth",
112
- label: "Depth:"
114
+ label: t('collections.viewMenu.depthLabel')
113
115
  }),
114
116
  /*#__PURE__*/ jsx(Select, {
115
117
  name: "populateDepth",
@@ -179,7 +181,7 @@ const ViewMenu = ({ collection, documentId, activeView, locale, depth, contentLo
179
181
  locale
180
182
  } : {}
181
183
  }),
182
- children: "Edit"
184
+ children: t('common.actions.edit')
183
185
  }),
184
186
  /*#__PURE__*/ jsx(Button, {
185
187
  size: "xs",
@@ -195,7 +197,7 @@ const ViewMenu = ({ collection, documentId, activeView, locale, depth, contentLo
195
197
  locale
196
198
  } : {}
197
199
  }),
198
- children: "API"
200
+ children: t('collections.viewMenu.apiButton')
199
201
  })
200
202
  ]
201
203
  });
@@ -0,0 +1,19 @@
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
+ * `@byline/host-tanstack-start/i18n` — host-side glue between the
10
+ * admin interface translation registry and TanStack Start's request
11
+ * model. Cookie helpers, the per-request locale resolver, and the
12
+ * server-side `resolveServerTranslator` companion to the client
13
+ * `useTranslation` hook.
14
+ */
15
+ export { ADMIN_LOCALE_COOKIE, clearAdminLocaleCookie, readAdminLocaleCookie, setAdminLocaleCookie, } from './locale-cookie.js';
16
+ export { buildLocaleDefinitions } from './locale-definitions.js';
17
+ export { resolveRequestLocale } from './resolve-locale.js';
18
+ export { resolveServerTranslator } from './server-translator.js';
19
+ export type { ServerTranslator } from './server-translator.js';
@@ -0,0 +1,4 @@
1
+ export { ADMIN_LOCALE_COOKIE, clearAdminLocaleCookie, readAdminLocaleCookie, setAdminLocaleCookie } from "./locale-cookie.js";
2
+ export { buildLocaleDefinitions } from "./locale-definitions.js";
3
+ export { resolveRequestLocale } from "./resolve-locale.js";
4
+ export { resolveServerTranslator } from "./server-translator.js";
@@ -0,0 +1,17 @@
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
+ export declare const ADMIN_LOCALE_COOKIE = "byline_admin_lng";
9
+ /** Read the current admin locale cookie, if any. */
10
+ export declare function readAdminLocaleCookie(): string | null;
11
+ /**
12
+ * Write the admin locale cookie. Caller is expected to have validated
13
+ * `locale` against the permitted `i18n.interface.locales` set first.
14
+ */
15
+ export declare function setAdminLocaleCookie(locale: string): void;
16
+ /** Clear the cookie — used when a user opts back into browser-default detection. */
17
+ export declare function clearAdminLocaleCookie(): void;
@@ -0,0 +1,26 @@
1
+ import { getCookie, setCookie } from "@tanstack/react-start/server";
2
+ const ADMIN_LOCALE_COOKIE = 'byline_admin_lng';
3
+ const ADMIN_LOCALE_MAX_AGE_SECONDS = 31536000;
4
+ const IS_PROD = 'production' === process.env.NODE_ENV;
5
+ function readAdminLocaleCookie() {
6
+ return getCookie(ADMIN_LOCALE_COOKIE) ?? null;
7
+ }
8
+ function setAdminLocaleCookie(locale) {
9
+ setCookie(ADMIN_LOCALE_COOKIE, locale, {
10
+ httpOnly: false,
11
+ sameSite: 'lax',
12
+ secure: IS_PROD,
13
+ path: '/',
14
+ maxAge: ADMIN_LOCALE_MAX_AGE_SECONDS
15
+ });
16
+ }
17
+ function clearAdminLocaleCookie() {
18
+ setCookie(ADMIN_LOCALE_COOKIE, '', {
19
+ httpOnly: false,
20
+ sameSite: 'lax',
21
+ secure: IS_PROD,
22
+ path: '/',
23
+ maxAge: 0
24
+ });
25
+ }
26
+ export { ADMIN_LOCALE_COOKIE, clearAdminLocaleCookie, readAdminLocaleCookie, setAdminLocaleCookie };
@@ -0,0 +1,29 @@
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
+ import type { LocaleDefinition } from '@byline/i18n';
9
+ /**
10
+ * Build a `LocaleDefinition[]` for the language switcher. Per-code
11
+ * resolution order:
12
+ *
13
+ * 1. An entry from the host's `i18n.interface.localeDefinitions`
14
+ * (matched by code). Wins outright — this is the path that lets a
15
+ * host author write `Français` instead of the lowercase
16
+ * `français` that CLDR's `Intl.DisplayNames` returns for romance
17
+ * languages.
18
+ * 2. `Intl.DisplayNames(code).of(code)` — produces a display name in
19
+ * each locale's own language using CLDR's data.
20
+ * 3. The raw code, as a last-resort fallback for exotic tags or
21
+ * runtimes that lack `Intl.DisplayNames`.
22
+ *
23
+ * Used by both the admin layout (post-auth) and the sign-in page
24
+ * (pre-auth) — anywhere `<LanguageMenu>` mounts.
25
+ */
26
+ export declare function buildLocaleDefinitions(codes: readonly string[], configured: ReadonlyArray<{
27
+ code: string;
28
+ nativeName: string;
29
+ }> | undefined): LocaleDefinition[];
@@ -0,0 +1,27 @@
1
+ function buildLocaleDefinitions(codes, configured) {
2
+ const explicit = new Map((configured ?? []).map((d)=>[
3
+ d.code,
4
+ d.nativeName
5
+ ]));
6
+ return codes.map((code)=>{
7
+ const explicitName = explicit.get(code);
8
+ if (null != explicitName) return {
9
+ code,
10
+ nativeName: explicitName
11
+ };
12
+ let nativeName = code;
13
+ try {
14
+ const dn = new Intl.DisplayNames([
15
+ code
16
+ ], {
17
+ type: 'language'
18
+ });
19
+ nativeName = dn.of(code) ?? code;
20
+ } catch {}
21
+ return {
22
+ code,
23
+ nativeName
24
+ };
25
+ });
26
+ }
27
+ export { buildLocaleDefinitions };
@@ -0,0 +1,20 @@
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
+ import type { LocaleCode } from '@byline/i18n';
9
+ /**
10
+ * Resolve the locale for the current request. Skips the
11
+ * `admin_users.preferred_locale` tier when no admin session is present —
12
+ * this is the right behaviour for pre-auth surfaces (sign-in page).
13
+ *
14
+ * Bypasses the auth resolver when `skipActorLookup` is true. Useful for
15
+ * very early-boot surfaces where the actor lookup itself is expensive
16
+ * or unwanted (rare; default to letting the cascade do its work).
17
+ */
18
+ export declare function resolveRequestLocale(options?: {
19
+ skipActorLookup?: boolean;
20
+ }): Promise<LocaleCode>;
@@ -0,0 +1,43 @@
1
+ import { getRequestHeader } from "@tanstack/react-start/server";
2
+ import { AuthError, AuthErrorCodes } from "@byline/auth";
3
+ import { resolveInterfaceLocale } from "@byline/i18n";
4
+ import { getAdminRequestContext } from "../auth/auth-context.js";
5
+ import { bylineCore } from "../integrations/byline-core.js";
6
+ import { readAdminLocaleCookie } from "./locale-cookie.js";
7
+ async function resolveRequestLocale(options) {
8
+ const core = bylineCore();
9
+ const { interface: ifaceConfig } = core.config.i18n;
10
+ let preferred = null;
11
+ if (!options?.skipActorLookup) preferred = await readPreferredLocaleFromActor();
12
+ return resolveInterfaceLocale({
13
+ locales: ifaceConfig.locales,
14
+ defaultLocale: ifaceConfig.defaultLocale,
15
+ preferred,
16
+ cookie: readAdminLocaleCookie(),
17
+ acceptLanguage: readAcceptLanguage()
18
+ });
19
+ }
20
+ async function readPreferredLocaleFromActor() {
21
+ try {
22
+ const context = await getAdminRequestContext();
23
+ const adminStore = bylineCore().adminStore;
24
+ if (null == adminStore) return null;
25
+ const actor = context.actor;
26
+ if (null == actor || 'object' != typeof actor || !('id' in actor)) return null;
27
+ const id = actor.id;
28
+ if ('string' != typeof id) return null;
29
+ const row = await adminStore.adminUsers.getById(id);
30
+ return row?.preferred_locale ?? null;
31
+ } catch (err) {
32
+ err instanceof AuthError && (err.code, AuthErrorCodes.UNAUTHENTICATED);
33
+ return null;
34
+ }
35
+ }
36
+ function readAcceptLanguage() {
37
+ try {
38
+ return getRequestHeader('accept-language') ?? null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+ export { resolveRequestLocale };
@@ -0,0 +1,33 @@
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
+ * `resolveServerTranslator(namespace)` — server-side companion to the
10
+ * client `useTranslation(namespace)` hook.
11
+ *
12
+ * Resolves the request locale via the same cascade the client hydrates
13
+ * against (preferred → cookie → Accept-Language → default), constructs
14
+ * a formatter bound to the registered translation bundle, and returns
15
+ * a `{ t, locale }` for the requested namespace. The shape mirrors
16
+ * `useTranslation` so callers can swap surfaces (loader vs component)
17
+ * without rethinking the API.
18
+ *
19
+ * Typical use:
20
+ *
21
+ * import { resolveServerTranslator } from '@byline/host-tanstack-start/i18n'
22
+ *
23
+ * export const sendInviteFn = createServerFn(...).handler(async () => {
24
+ * const { t } = await resolveServerTranslator('byline-admin')
25
+ * return { subject: t('email.invite.subject') }
26
+ * })
27
+ */
28
+ import { type LocaleCode } from '@byline/i18n';
29
+ export interface ServerTranslator {
30
+ t: (key: string, values?: Record<string, string | number | boolean | Date | null>) => string;
31
+ locale: LocaleCode;
32
+ }
33
+ export declare function resolveServerTranslator(namespace: string): Promise<ServerTranslator>;
@@ -0,0 +1,19 @@
1
+ import { createFormatter } from "@byline/i18n";
2
+ import { bylineCore } from "../integrations/byline-core.js";
3
+ import { resolveRequestLocale } from "./resolve-locale.js";
4
+ async function resolveServerTranslator(namespace) {
5
+ const core = bylineCore();
6
+ const { interface: ifaceConfig, translations: bundle } = core.config.i18n;
7
+ if (null == bundle) throw new Error("[resolveServerTranslator] no translation bundle is registered. Pass `translations: adminTranslations({ en: true })` (or a merged bundle) on `i18n` in your config.");
8
+ const activeLocale = await resolveRequestLocale();
9
+ const formatter = createFormatter({
10
+ bundle,
11
+ activeLocale,
12
+ defaultLocale: ifaceConfig.defaultLocale
13
+ });
14
+ return {
15
+ t: (key, values)=>formatter.t(namespace, key, values),
16
+ locale: activeLocale
17
+ };
18
+ }
19
+ export { resolveServerTranslator };
@@ -4,6 +4,7 @@ import { createAdminRole, updateAdminRole } from "../server-fns/admin-roles/inde
4
4
  import { createAdminUser, setAdminUserPassword, setUserRoles, updateAdminUser } from "../server-fns/admin-users/index.js";
5
5
  import { adminSignIn } from "../server-fns/auth/index.js";
6
6
  import { getCollectionDocumentVersion } from "../server-fns/collections/get.js";
7
+ import { setInterfaceLocaleFn } from "../server-fns/i18n/index.js";
7
8
  const byline_admin_services_getCollectionDocumentVersion = async (collection, documentId, versionId, locale)=>{
8
9
  const result = await getCollectionDocumentVersion(collection, documentId, versionId, locale);
9
10
  if (null == result) throw new Error(`Document version not found: collection=${collection} document=${documentId} version=${versionId}`);
@@ -13,6 +14,7 @@ const bylineAdminServices = {
13
14
  adminSignIn: adminSignIn,
14
15
  updateAccount: updateAccount,
15
16
  changeAccountPassword: changeAccountPassword,
17
+ setInterfaceLocale: setInterfaceLocaleFn,
16
18
  createAdminUser: createAdminUser,
17
19
  updateAdminUser: updateAdminUser,
18
20
  setAdminUserPassword: setAdminUserPassword,
@@ -8,11 +8,11 @@
8
8
  /**
9
9
  * Host-side adapters that bind the webapp's TanStack Start server functions
10
10
  * to the framework-neutral `BylineFieldServices` contract consumed by
11
- * `@byline/ui` field/form components.
11
+ * `@byline/admin` field/form components.
12
12
  *
13
13
  * Wired into the admin route once via `<BylineFieldServicesProvider>`. A
14
14
  * future Next.js host would ship its own adapter file and Provider; the
15
- * @byline/ui surface is unchanged.
15
+ * @byline/admin surface is unchanged.
16
16
  */
17
- import type { BylineFieldServices } from '@byline/ui/react';
17
+ import type { BylineFieldServices } from '@byline/admin/react';
18
18
  export declare const bylineFieldServices: BylineFieldServices;
@@ -1,6 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createFileRoute } from "@tanstack/react-router";
3
3
  import { AccountSelfContainer } from "@byline/admin/admin-account/components/container";
4
+ import { useTranslation } from "@byline/i18n/react";
4
5
  import { Container, Section } from "@byline/ui/react";
5
6
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
6
7
  import { getAccount } from "../server-fns/admin-account/index.js";
@@ -23,16 +24,17 @@ function createAdminAccountRoute(path) {
23
24
  },
24
25
  component: function() {
25
26
  const { account } = Route.useLoaderData();
27
+ const { t } = useTranslation('byline-admin');
26
28
  return /*#__PURE__*/ jsxs(Fragment, {
27
29
  children: [
28
30
  /*#__PURE__*/ jsx(BreadcrumbsClient, {
29
31
  breadcrumbs: [
30
32
  {
31
- label: 'Dashboard',
33
+ label: t('chrome.menu.dashboard'),
32
34
  href: '/admin'
33
35
  },
34
36
  {
35
- label: 'Account',
37
+ label: t('chrome.account'),
36
38
  href: '/admin/account'
37
39
  }
38
40
  ]
@@ -40,6 +42,7 @@ function createAdminAccountRoute(path) {
40
42
  /*#__PURE__*/ jsx(Section, {
41
43
  className: "pb-2",
42
44
  children: /*#__PURE__*/ jsxs(Container, {
45
+ className: "mb-2",
43
46
  children: [
44
47
  /*#__PURE__*/ jsx("h1", {
45
48
  className: "mb-2",
@@ -47,7 +50,7 @@ function createAdminAccountRoute(path) {
47
50
  }),
48
51
  /*#__PURE__*/ jsx("p", {
49
52
  className: "muted",
50
- children: "Manage your own profile and password."
53
+ children: t('account.intro')
51
54
  })
52
55
  ]
53
56
  })
@@ -1,6 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createFileRoute } from "@tanstack/react-router";
3
3
  import { getClientConfig } from "@byline/core";
4
+ import { useTranslation } from "@byline/i18n/react";
4
5
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
5
6
  import { AdminDashboard } from "../admin-shell/chrome/dashboard.js";
6
7
  import { getCollectionStats } from "../server-fns/collections/index.js";
@@ -22,12 +23,13 @@ function createAdminDashboardRoute(path) {
22
23
  },
23
24
  component: function() {
24
25
  const { statsMap } = Route.useLoaderData();
26
+ const { t } = useTranslation('byline-admin');
25
27
  return /*#__PURE__*/ jsxs(Fragment, {
26
28
  children: [
27
29
  /*#__PURE__*/ jsx(BreadcrumbsClient, {
28
30
  breadcrumbs: [
29
31
  {
30
- label: 'Dashboard',
32
+ label: t('chrome.menu.dashboard'),
31
33
  href: '/admin'
32
34
  }
33
35
  ]
@@ -1,7 +1,9 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
3
+ import { BylineFieldServicesProvider } from "@byline/admin/react";
3
4
  import { BylineAdminServicesProvider } from "@byline/admin/services";
4
- import { BylineFieldServicesProvider } from "@byline/ui/react";
5
+ import { getClientConfig } from "@byline/core";
6
+ import { I18nProvider } from "@byline/i18n/react";
5
7
  import classnames from "classnames";
6
8
  import { AdminAppBar } from "../admin-shell/chrome/admin-app-bar.js";
7
9
  import admin_layout_module from "../admin-shell/chrome/admin-layout.module.js";
@@ -11,18 +13,22 @@ import { AdminMenuDrawer } from "../admin-shell/chrome/menu-drawer.js";
11
13
  import { AdminMenuProvider } from "../admin-shell/chrome/menu-provider.js";
12
14
  import { RouteError, RouteNotFound } from "../admin-shell/chrome/route-error.js";
13
15
  import { RouteProgressBar } from "../admin-shell/chrome/route-progress-bar.js";
16
+ import { buildLocaleDefinitions } from "../i18n/locale-definitions.js";
14
17
  import { bylineAdminServices } from "../integrations/byline-admin-services.js";
15
18
  import { BylineAiAdminProvider } from "../integrations/byline-ai.js";
16
19
  import { bylineFieldServices } from "../integrations/byline-field-services.js";
17
20
  import { getCurrentAdminUser } from "../server-fns/auth/index.js";
21
+ import { getActiveLocaleFn, setInterfaceLocaleFn } from "../server-fns/i18n/index.js";
18
22
  function createAdminLayoutRoute(path, opts = {}) {
19
23
  const signInPath = opts.signInPath ?? '/sign-in';
20
24
  const Route = createFileRoute(path)({
21
25
  beforeLoad: async ({ location })=>{
22
26
  try {
23
27
  const user = await getCurrentAdminUser();
28
+ const activeLocale = await getActiveLocaleFn();
24
29
  return {
25
- user
30
+ user,
31
+ activeLocale
26
32
  };
27
33
  } catch {
28
34
  throw redirect({
@@ -34,29 +40,46 @@ function createAdminLayoutRoute(path, opts = {}) {
34
40
  }
35
41
  },
36
42
  component: function() {
37
- const { user } = Route.useRouteContext();
38
- return /*#__PURE__*/ jsx(BylineAdminServicesProvider, {
39
- services: bylineAdminServices,
40
- children: /*#__PURE__*/ jsx(BylineFieldServicesProvider, {
41
- services: bylineFieldServices,
42
- children: /*#__PURE__*/ jsx(BylineAiAdminProvider, {
43
- children: /*#__PURE__*/ jsxs(AdminMenuProvider, {
44
- children: [
45
- /*#__PURE__*/ jsx(RouteProgressBar, {}),
46
- /*#__PURE__*/ jsx(AdminAppBar, {
47
- user: user
48
- }),
49
- /*#__PURE__*/ jsxs("main", {
50
- className: classnames('byline-admin-layout-main', admin_layout_module.main),
51
- children: [
52
- /*#__PURE__*/ jsx(DrawerToggle, {}),
53
- /*#__PURE__*/ jsx(AdminMenuDrawer, {}),
54
- /*#__PURE__*/ jsx(Content, {
55
- children: /*#__PURE__*/ jsx(Outlet, {})
56
- })
57
- ]
58
- })
59
- ]
43
+ const { user, activeLocale } = Route.useRouteContext();
44
+ const { i18n } = getClientConfig();
45
+ const localeDefinitions = buildLocaleDefinitions(i18n.interface.locales, i18n.interface.localeDefinitions);
46
+ const handleSetLocale = async (next)=>{
47
+ await setInterfaceLocaleFn({
48
+ data: {
49
+ locale: next
50
+ }
51
+ });
52
+ window.location.reload();
53
+ };
54
+ return /*#__PURE__*/ jsx(I18nProvider, {
55
+ bundle: i18n.translations ?? {},
56
+ activeLocale: activeLocale,
57
+ defaultLocale: i18n.interface.defaultLocale,
58
+ localeDefinitions: localeDefinitions,
59
+ setLocale: handleSetLocale,
60
+ children: /*#__PURE__*/ jsx(BylineAdminServicesProvider, {
61
+ services: bylineAdminServices,
62
+ children: /*#__PURE__*/ jsx(BylineFieldServicesProvider, {
63
+ services: bylineFieldServices,
64
+ children: /*#__PURE__*/ jsx(BylineAiAdminProvider, {
65
+ children: /*#__PURE__*/ jsxs(AdminMenuProvider, {
66
+ children: [
67
+ /*#__PURE__*/ jsx(RouteProgressBar, {}),
68
+ /*#__PURE__*/ jsx(AdminAppBar, {
69
+ user: user
70
+ }),
71
+ /*#__PURE__*/ jsxs("main", {
72
+ className: classnames('byline-admin-layout-main', admin_layout_module.main),
73
+ children: [
74
+ /*#__PURE__*/ jsx(DrawerToggle, {}),
75
+ /*#__PURE__*/ jsx(AdminMenuDrawer, {}),
76
+ /*#__PURE__*/ jsx(Content, {
77
+ children: /*#__PURE__*/ jsx(Outlet, {})
78
+ })
79
+ ]
80
+ })
81
+ ]
82
+ })
60
83
  })
61
84
  })
62
85
  })