@byline/host-tanstack-start 2.5.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-shell/admin-roles/container.js +38 -24
- package/dist/admin-shell/admin-roles/delete.js +9 -7
- package/dist/admin-shell/admin-roles/list.js +20 -16
- package/dist/admin-shell/admin-users/container.js +79 -56
- package/dist/admin-shell/admin-users/delete.js +10 -8
- package/dist/admin-shell/admin-users/list.js +27 -18
- package/dist/admin-shell/chrome/admin-app-bar.js +5 -2
- package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs.js +3 -1
- package/dist/admin-shell/chrome/dashboard.js +13 -11
- package/dist/admin-shell/chrome/hamburger.js +3 -1
- package/dist/admin-shell/chrome/menu-drawer.js +7 -5
- package/dist/admin-shell/chrome/preview-toggle.js +5 -3
- package/dist/admin-shell/chrome/route-error.d.ts +3 -2
- package/dist/admin-shell/chrome/route-error.js +29 -22
- package/dist/admin-shell/chrome/sign-in-page.d.ts +16 -4
- package/dist/admin-shell/chrome/sign-in-page.js +38 -13
- package/dist/admin-shell/chrome/sign-in-page.module.js +1 -0
- package/dist/admin-shell/chrome/sign-in-page_module.css +8 -1
- package/dist/admin-shell/collections/api.js +6 -5
- package/dist/admin-shell/collections/create.js +12 -4
- package/dist/admin-shell/collections/edit.js +112 -37
- package/dist/admin-shell/collections/history.js +17 -12
- package/dist/admin-shell/collections/list.js +18 -13
- package/dist/admin-shell/collections/preview-link.d.ts +1 -10
- package/dist/admin-shell/collections/preview-link.js +9 -11
- package/dist/admin-shell/collections/resolve-preview-url.d.ts +34 -0
- package/dist/admin-shell/collections/resolve-preview-url.js +17 -0
- package/dist/admin-shell/collections/restore-version-modal.js +13 -14
- package/dist/admin-shell/collections/tanstack-navigation-guard.d.ts +1 -1
- package/dist/admin-shell/collections/view-menu.js +7 -5
- package/dist/i18n/index.d.ts +19 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/locale-cookie.d.ts +17 -0
- package/dist/i18n/locale-cookie.js +26 -0
- package/dist/i18n/locale-definitions.d.ts +29 -0
- package/dist/i18n/locale-definitions.js +27 -0
- package/dist/i18n/resolve-locale.d.ts +20 -0
- package/dist/i18n/resolve-locale.js +43 -0
- package/dist/i18n/server-translator.d.ts +33 -0
- package/dist/i18n/server-translator.js +19 -0
- package/dist/integrations/byline-admin-services.js +2 -0
- package/dist/integrations/byline-field-services.d.ts +3 -3
- package/dist/routes/create-admin-account-route.js +6 -3
- package/dist/routes/create-admin-dashboard-route.js +3 -1
- package/dist/routes/create-admin-layout-route.js +48 -25
- package/dist/routes/create-admin-permissions-route.js +4 -2
- package/dist/routes/create-admin-role-edit-route.js +5 -3
- package/dist/routes/create-admin-roles-list-route.js +4 -2
- package/dist/routes/create-admin-user-edit-route.js +5 -3
- package/dist/routes/create-admin-users-list-route.js +4 -2
- package/dist/routes/create-collection-api-route.js +5 -3
- package/dist/routes/create-collection-create-route.js +4 -2
- package/dist/routes/create-collection-edit-route.js +4 -2
- package/dist/routes/create-collection-history-route.js +5 -3
- package/dist/routes/create-collection-list-route.js +11 -5
- package/dist/routes/create-sign-in-route.js +10 -1
- package/dist/server-fns/admin-account/change-password.d.ts +1 -0
- package/dist/server-fns/admin-account/get.d.ts +1 -0
- package/dist/server-fns/admin-account/update.d.ts +1 -0
- package/dist/server-fns/admin-users/create.d.ts +1 -0
- package/dist/server-fns/admin-users/get.d.ts +1 -0
- package/dist/server-fns/admin-users/list.d.ts +1 -0
- package/dist/server-fns/admin-users/set-password.d.ts +1 -0
- package/dist/server-fns/admin-users/update.d.ts +1 -0
- package/dist/server-fns/auth/sign-in.js +18 -0
- package/dist/server-fns/i18n/get-active-locale.d.ts +8 -0
- package/dist/server-fns/i18n/get-active-locale.js +6 -0
- package/dist/server-fns/i18n/index.d.ts +10 -0
- package/dist/server-fns/i18n/index.js +2 -0
- package/dist/server-fns/i18n/set-locale.d.ts +25 -0
- package/dist/server-fns/i18n/set-locale.js +42 -0
- package/package.json +16 -7
- package/src/admin-shell/admin-roles/container.tsx +41 -31
- package/src/admin-shell/admin-roles/delete.tsx +10 -11
- package/src/admin-shell/admin-roles/list.tsx +29 -16
- package/src/admin-shell/admin-users/container.tsx +77 -50
- package/src/admin-shell/admin-users/delete.tsx +11 -12
- package/src/admin-shell/admin-users/list.tsx +39 -18
- package/src/admin-shell/chrome/admin-app-bar.tsx +5 -2
- package/src/admin-shell/chrome/breadcrumbs/breadcrumbs.tsx +3 -1
- package/src/admin-shell/chrome/dashboard.tsx +9 -3
- package/src/admin-shell/chrome/hamburger.tsx +3 -1
- package/src/admin-shell/chrome/menu-drawer.tsx +7 -5
- package/src/admin-shell/chrome/preview-toggle.tsx +6 -4
- package/src/admin-shell/chrome/route-error.tsx +39 -26
- package/src/admin-shell/chrome/sign-in-page.module.css +10 -1
- package/src/admin-shell/chrome/sign-in-page.tsx +46 -12
- package/src/admin-shell/collections/api.tsx +5 -1
- package/src/admin-shell/collections/create.tsx +10 -4
- package/src/admin-shell/collections/edit.tsx +79 -72
- package/src/admin-shell/collections/history.tsx +18 -12
- package/src/admin-shell/collections/list.tsx +25 -14
- package/src/admin-shell/collections/preview-link.tsx +20 -33
- package/src/admin-shell/collections/resolve-preview-url.test.node.ts +167 -0
- package/src/admin-shell/collections/resolve-preview-url.ts +67 -0
- package/src/admin-shell/collections/restore-version-modal.tsx +11 -12
- package/src/admin-shell/collections/tanstack-navigation-guard.ts +1 -1
- package/src/admin-shell/collections/view-menu.tsx +9 -5
- package/src/i18n/index.ts +26 -0
- package/src/i18n/locale-cookie.ts +68 -0
- package/src/i18n/locale-definitions.ts +48 -0
- package/src/i18n/resolve-locale.ts +96 -0
- package/src/i18n/server-translator.ts +60 -0
- package/src/integrations/byline-admin-services.ts +2 -0
- package/src/integrations/byline-field-services.ts +7 -3
- package/src/routes/create-admin-account-route.tsx +6 -4
- package/src/routes/create-admin-dashboard-route.tsx +5 -1
- package/src/routes/create-admin-layout-route.tsx +53 -20
- package/src/routes/create-admin-permissions-route.tsx +4 -2
- package/src/routes/create-admin-role-edit-route.tsx +5 -3
- package/src/routes/create-admin-roles-list-route.tsx +5 -2
- package/src/routes/create-admin-user-edit-route.tsx +5 -3
- package/src/routes/create-admin-users-list-route.tsx +4 -2
- package/src/routes/create-collection-api-route.tsx +5 -3
- package/src/routes/create-collection-create-route.tsx +7 -2
- package/src/routes/create-collection-edit-route.tsx +4 -2
- package/src/routes/create-collection-history-route.tsx +5 -3
- package/src/routes/create-collection-list-route.tsx +8 -10
- package/src/routes/create-sign-in-route.tsx +14 -1
- package/src/server-fns/auth/sign-in.ts +45 -0
- package/src/server-fns/i18n/get-active-locale.ts +26 -0
- package/src/server-fns/i18n/index.ts +11 -0
- 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('
|
|
39
|
-
else if ('ERR_NOT_FOUND' === code) setError('
|
|
40
|
-
else 'ERR_FORBIDDEN' === code || 'ERR_UNAUTHENTICATED' === code ? setError('
|
|
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:
|
|
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:
|
|
73
|
+
children: t('collections.restore.createdLabel')
|
|
72
74
|
}),
|
|
73
75
|
" ",
|
|
74
76
|
versionLabel
|
|
75
77
|
]
|
|
76
78
|
}),
|
|
77
|
-
/*#__PURE__*/
|
|
79
|
+
/*#__PURE__*/ jsx("p", {
|
|
78
80
|
className: classnames('byline-coll-restore-warning', restore_version_modal_module.warning),
|
|
79
|
-
children:
|
|
80
|
-
|
|
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:
|
|
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
|
-
}) : '
|
|
120
|
+
}) : t('collections.restore.confirmButton')
|
|
122
121
|
})
|
|
123
122
|
]
|
|
124
123
|
})
|
|
@@ -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:
|
|
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: '
|
|
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:
|
|
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:
|
|
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:
|
|
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/
|
|
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/
|
|
15
|
+
* @byline/admin surface is unchanged.
|
|
16
16
|
*/
|
|
17
|
-
import type { BylineFieldServices } from '@byline/
|
|
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: '
|
|
33
|
+
label: t('chrome.menu.dashboard'),
|
|
32
34
|
href: '/admin'
|
|
33
35
|
},
|
|
34
36
|
{
|
|
35
|
-
label: '
|
|
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:
|
|
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: '
|
|
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 {
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
})
|