talizen 0.2.12 → 0.2.14
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/README.md +8 -0
- package/auth.d.ts +17 -0
- package/auth.js +76 -1
- package/core.d.ts +8 -0
- package/core.js +8 -0
- package/i18n.d.ts +4 -10
- package/i18n.js +31 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,6 +120,7 @@ import {
|
|
|
120
120
|
logout,
|
|
121
121
|
register,
|
|
122
122
|
updateProfile,
|
|
123
|
+
useAuth,
|
|
123
124
|
} from "talizen/auth";
|
|
124
125
|
|
|
125
126
|
await register({
|
|
@@ -140,6 +141,13 @@ const providers = await listAuthProviders();
|
|
|
140
141
|
console.log(providers.map((provider) => provider.key));
|
|
141
142
|
|
|
142
143
|
await loginWithOAuth("github", { redirectUrl: "/account" });
|
|
144
|
+
|
|
145
|
+
function AccountBadge() {
|
|
146
|
+
const { user, loading, logout } = useAuth();
|
|
147
|
+
if (loading) return <span>Loading...</span>;
|
|
148
|
+
if (!user) return <a href="/login">Sign in</a>;
|
|
149
|
+
return <button onClick={() => logout()}>{user.name ?? user.account ?? "Logout"}</button>;
|
|
150
|
+
}
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
### Invoke a custom function
|
package/auth.d.ts
CHANGED
|
@@ -54,6 +54,23 @@ export declare class TalizenAuthError extends Error {
|
|
|
54
54
|
}
|
|
55
55
|
export declare function register(input: AuthRegisterInput, options?: TalizenRequestOptions): Promise<AuthUser>;
|
|
56
56
|
export declare function login(input: AuthPasswordInput, options?: TalizenRequestOptions): Promise<AuthUser>;
|
|
57
|
+
export interface UseAuthResult {
|
|
58
|
+
user: AuthUser | null;
|
|
59
|
+
loading: boolean;
|
|
60
|
+
error: unknown;
|
|
61
|
+
refresh(): Promise<AuthUser | null>;
|
|
62
|
+
login(input: AuthPasswordInput): Promise<AuthUser>;
|
|
63
|
+
register(input: AuthRegisterInput): Promise<AuthUser>;
|
|
64
|
+
logout(): Promise<void>;
|
|
65
|
+
updateProfile(profile: AuthProfile): Promise<AuthUser>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe to the current auth user.
|
|
69
|
+
*
|
|
70
|
+
* The hook reloads `/auth/me` whenever `setTalizenConfig()` changes runtime
|
|
71
|
+
* config, which lets preview auth headers update the page without remounting.
|
|
72
|
+
*/
|
|
73
|
+
export declare function useAuth(options?: TalizenRequestOptions): UseAuthResult;
|
|
57
74
|
export declare function logout(options?: TalizenRequestOptions): Promise<void>;
|
|
58
75
|
export declare function currentUser(options?: TalizenRequestOptions): Promise<AuthUser | null>;
|
|
59
76
|
export declare function updateProfile(profile: AuthProfile, options?: TalizenRequestOptions): Promise<AuthUser>;
|
package/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { getTalizenConfig, requestJson, subscribeTalizenConfig, } from "./core.js";
|
|
2
3
|
export class TalizenAuthError extends Error {
|
|
3
4
|
constructor(message) {
|
|
4
5
|
super(message);
|
|
@@ -17,6 +18,80 @@ export async function login(input, options) {
|
|
|
17
18
|
body: JSON.stringify(input),
|
|
18
19
|
}, options);
|
|
19
20
|
}
|
|
21
|
+
function readAuthConfigSnapshot() {
|
|
22
|
+
return getTalizenConfig();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Subscribe to the current auth user.
|
|
26
|
+
*
|
|
27
|
+
* The hook reloads `/auth/me` whenever `setTalizenConfig()` changes runtime
|
|
28
|
+
* config, which lets preview auth headers update the page without remounting.
|
|
29
|
+
*/
|
|
30
|
+
export function useAuth(options) {
|
|
31
|
+
const configSnapshot = React.useSyncExternalStore(subscribeTalizenConfig, readAuthConfigSnapshot, readAuthConfigSnapshot);
|
|
32
|
+
const optionsRef = React.useRef(options);
|
|
33
|
+
const requestIdRef = React.useRef(0);
|
|
34
|
+
const [state, setState] = React.useState({
|
|
35
|
+
user: null,
|
|
36
|
+
loading: true,
|
|
37
|
+
error: null,
|
|
38
|
+
});
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
optionsRef.current = options;
|
|
41
|
+
}, [options]);
|
|
42
|
+
const refresh = React.useCallback(async () => {
|
|
43
|
+
const requestId = requestIdRef.current + 1;
|
|
44
|
+
requestIdRef.current = requestId;
|
|
45
|
+
setState(current => ({ ...current, loading: true, error: null }));
|
|
46
|
+
try {
|
|
47
|
+
const nextUser = await currentUser(optionsRef.current);
|
|
48
|
+
if (requestIdRef.current === requestId) {
|
|
49
|
+
setState({ user: nextUser, loading: false, error: null });
|
|
50
|
+
}
|
|
51
|
+
return nextUser;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (requestIdRef.current === requestId) {
|
|
55
|
+
setState(current => ({ ...current, loading: false, error }));
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}, []);
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
void refresh().catch(() => { });
|
|
62
|
+
}, [configSnapshot, refresh]);
|
|
63
|
+
const loginAndRefresh = React.useCallback(async (input) => {
|
|
64
|
+
const nextUser = await login(input, optionsRef.current);
|
|
65
|
+
requestIdRef.current += 1;
|
|
66
|
+
setState({ user: nextUser, loading: false, error: null });
|
|
67
|
+
return nextUser;
|
|
68
|
+
}, []);
|
|
69
|
+
const registerAndRefresh = React.useCallback(async (input) => {
|
|
70
|
+
const nextUser = await register(input, optionsRef.current);
|
|
71
|
+
requestIdRef.current += 1;
|
|
72
|
+
setState({ user: nextUser, loading: false, error: null });
|
|
73
|
+
return nextUser;
|
|
74
|
+
}, []);
|
|
75
|
+
const logoutAndRefresh = React.useCallback(async () => {
|
|
76
|
+
await logout(optionsRef.current);
|
|
77
|
+
requestIdRef.current += 1;
|
|
78
|
+
setState({ user: null, loading: false, error: null });
|
|
79
|
+
}, []);
|
|
80
|
+
const updateProfileAndRefresh = React.useCallback(async (profile) => {
|
|
81
|
+
const nextUser = await updateProfile(profile, optionsRef.current);
|
|
82
|
+
requestIdRef.current += 1;
|
|
83
|
+
setState({ user: nextUser, loading: false, error: null });
|
|
84
|
+
return nextUser;
|
|
85
|
+
}, []);
|
|
86
|
+
return {
|
|
87
|
+
...state,
|
|
88
|
+
refresh,
|
|
89
|
+
login: loginAndRefresh,
|
|
90
|
+
register: registerAndRefresh,
|
|
91
|
+
logout: logoutAndRefresh,
|
|
92
|
+
updateProfile: updateProfileAndRefresh,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
20
95
|
export async function logout(options) {
|
|
21
96
|
await requestJson("/auth/logout", {
|
|
22
97
|
method: "POST",
|
package/core.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface TalizenClientConfig {
|
|
|
17
17
|
headers?: HeadersInit;
|
|
18
18
|
fetch?: typeof fetch;
|
|
19
19
|
onFileUploadProcess?: TalizenFileUploadProcessCallback;
|
|
20
|
+
i18n?: Partial<TalizenLocaleRuntime>;
|
|
21
|
+
messages?: Record<string, unknown>;
|
|
20
22
|
}
|
|
21
23
|
export interface TalizenRequestOptions extends TalizenClientConfig {
|
|
22
24
|
signal?: AbortSignal;
|
|
@@ -25,8 +27,14 @@ export interface TalizenErrorBody {
|
|
|
25
27
|
code?: number | string;
|
|
26
28
|
message?: string;
|
|
27
29
|
}
|
|
30
|
+
export interface TalizenLocaleRuntime {
|
|
31
|
+
locale: string;
|
|
32
|
+
locales: string[];
|
|
33
|
+
defaultLocale: string;
|
|
34
|
+
}
|
|
28
35
|
export declare function setTalizenConfig(config: TalizenClientConfig): void;
|
|
29
36
|
export declare function getTalizenConfig(): TalizenClientConfig;
|
|
37
|
+
export declare function subscribeTalizenConfig(listener: () => void): () => void;
|
|
30
38
|
declare global {
|
|
31
39
|
var TalizenConfig: TalizenClientConfig | undefined;
|
|
32
40
|
}
|
package/core.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
let talizenConfig = {};
|
|
2
|
+
const talizenConfigListeners = new Set();
|
|
2
3
|
export function setTalizenConfig(config) {
|
|
3
4
|
talizenConfig = {
|
|
4
5
|
...talizenConfig,
|
|
5
6
|
...config,
|
|
6
7
|
};
|
|
8
|
+
talizenConfigListeners.forEach(listener => listener());
|
|
7
9
|
}
|
|
8
10
|
export function getTalizenConfig() {
|
|
9
11
|
return talizenConfig;
|
|
10
12
|
}
|
|
13
|
+
export function subscribeTalizenConfig(listener) {
|
|
14
|
+
talizenConfigListeners.add(listener);
|
|
15
|
+
return () => {
|
|
16
|
+
talizenConfigListeners.delete(listener);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
11
19
|
export function resolveTalizenConfig(config) {
|
|
12
20
|
// globalThis is the global object in the browser and node.js
|
|
13
21
|
const globalTalizenConfig = typeof globalThis !== "undefined" ? globalThis.TalizenConfig : {};
|
package/i18n.d.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { type TalizenLocaleRuntime } from "./core.js";
|
|
2
3
|
/**
|
|
3
|
-
* 当前语言运行时信息。由 Talizen
|
|
4
|
-
*
|
|
4
|
+
* 当前语言运行时信息。由 Talizen 渲染引擎写入 TalizenConfig.i18n
|
|
5
|
+
* (旧版 globalThis.__TALIZEN_I18N__ 仍作为兼容 fallback)。站点未开启多语言时为空。
|
|
5
6
|
*/
|
|
6
|
-
export
|
|
7
|
-
/** 当前生效语言;站点未配置多语言时为 ""。 */
|
|
8
|
-
locale: string;
|
|
9
|
-
/** 站点配置的全部语言。 */
|
|
10
|
-
locales: string[];
|
|
11
|
-
/** 默认语言(其 URL 无前缀)。 */
|
|
12
|
-
defaultLocale: string;
|
|
13
|
-
}
|
|
7
|
+
export type LocaleRuntime = TalizenLocaleRuntime;
|
|
14
8
|
declare global {
|
|
15
9
|
var __TALIZEN_I18N__: Partial<LocaleRuntime> | undefined;
|
|
16
10
|
}
|
package/i18n.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { getTalizenConfig, subscribeTalizenConfig } from "./core.js";
|
|
3
|
+
let lastSnapshot = null;
|
|
4
|
+
let lastSnapshotI18n;
|
|
5
|
+
let lastSnapshotMessages;
|
|
6
|
+
function readI18nRuntimeConfig() {
|
|
7
|
+
const config = getTalizenConfig();
|
|
8
|
+
const fallbackI18n = typeof globalThis !== "undefined" ? globalThis.__TALIZEN_I18N__ : undefined;
|
|
9
|
+
const fallbackMessages = typeof globalThis !== "undefined" ? globalThis.__TALIZEN_MESSAGES__ : undefined;
|
|
10
|
+
const i18n = config.i18n ?? fallbackI18n;
|
|
11
|
+
const messages = config.messages ?? fallbackMessages ?? {};
|
|
12
|
+
if (lastSnapshot && lastSnapshotI18n === i18n && lastSnapshotMessages === messages) {
|
|
13
|
+
return lastSnapshot;
|
|
14
|
+
}
|
|
15
|
+
lastSnapshotI18n = i18n;
|
|
16
|
+
lastSnapshotMessages = messages;
|
|
17
|
+
lastSnapshot = { i18n, messages };
|
|
18
|
+
return lastSnapshot;
|
|
19
|
+
}
|
|
20
|
+
function normalizeLocaleRuntime(value) {
|
|
21
|
+
const d = value ?? {};
|
|
4
22
|
const locales = Array.isArray(d.locales) ? d.locales.filter((l) => typeof l === "string") : [];
|
|
5
23
|
return {
|
|
6
24
|
locale: typeof d.locale === "string" ? d.locale : "",
|
|
@@ -8,6 +26,9 @@ function readLocaleRuntime() {
|
|
|
8
26
|
defaultLocale: typeof d.defaultLocale === "string" ? d.defaultLocale : (locales[0] ?? ""),
|
|
9
27
|
};
|
|
10
28
|
}
|
|
29
|
+
function readLocaleRuntime() {
|
|
30
|
+
return normalizeLocaleRuntime(readI18nRuntimeConfig().i18n);
|
|
31
|
+
}
|
|
11
32
|
/**
|
|
12
33
|
* 读取当前语言运行时信息(当前语言 / 全部语言 / 默认语言)。SSR 与客户端返回一致。
|
|
13
34
|
* 站点未开启多语言时 `locale === ""`。
|
|
@@ -17,7 +38,8 @@ function readLocaleRuntime() {
|
|
|
17
38
|
* ```
|
|
18
39
|
*/
|
|
19
40
|
export function useLocale() {
|
|
20
|
-
|
|
41
|
+
const snapshot = React.useSyncExternalStore(subscribeTalizenConfig, readI18nRuntimeConfig, readI18nRuntimeConfig);
|
|
42
|
+
return normalizeLocaleRuntime(snapshot.i18n);
|
|
21
43
|
}
|
|
22
44
|
/**
|
|
23
45
|
* 读取当前语言运行时信息——与 useLocale() 完全等价,但命名为 get*(**不是 hook**),
|
|
@@ -67,6 +89,7 @@ export const LOCALE_COOKIE = "CREGHT_LOCALE";
|
|
|
67
89
|
*/
|
|
68
90
|
export function Link(props) {
|
|
69
91
|
const { href, locale, onClick, ...rest } = props;
|
|
92
|
+
useLocale();
|
|
70
93
|
const finalHref = localizedPath(href, locale);
|
|
71
94
|
const handleClick = (event) => {
|
|
72
95
|
if (locale && typeof document !== "undefined") {
|
|
@@ -77,7 +100,7 @@ export function Link(props) {
|
|
|
77
100
|
return React.createElement("a", { ...rest, href: finalHref, onClick: handleClick });
|
|
78
101
|
}
|
|
79
102
|
function readMessages() {
|
|
80
|
-
return (
|
|
103
|
+
return readI18nRuntimeConfig().messages;
|
|
81
104
|
}
|
|
82
105
|
function lookupMessage(obj, path) {
|
|
83
106
|
let cur = obj;
|
|
@@ -99,8 +122,8 @@ function lookupMessage(obj, path) {
|
|
|
99
122
|
*
|
|
100
123
|
* UI chrome 用 useTranslations;文章等内容用 CMS 字段级 _i18n(见 listContents/getContent)。
|
|
101
124
|
*/
|
|
102
|
-
function buildTranslator(namespace) {
|
|
103
|
-
const messages = readMessages();
|
|
125
|
+
function buildTranslator(namespace, sourceMessages) {
|
|
126
|
+
const messages = sourceMessages ?? readMessages();
|
|
104
127
|
const scoped = namespace ? lookupMessage(messages, namespace) : messages;
|
|
105
128
|
const base = (scoped && typeof scoped === "object" ? scoped : {});
|
|
106
129
|
return (key, vars) => {
|
|
@@ -112,7 +135,8 @@ function buildTranslator(namespace) {
|
|
|
112
135
|
};
|
|
113
136
|
}
|
|
114
137
|
export function useTranslations(namespace) {
|
|
115
|
-
|
|
138
|
+
const snapshot = React.useSyncExternalStore(subscribeTalizenConfig, readI18nRuntimeConfig, readI18nRuntimeConfig);
|
|
139
|
+
return React.useMemo(() => buildTranslator(namespace, snapshot.messages), [namespace, snapshot]);
|
|
116
140
|
}
|
|
117
141
|
/**
|
|
118
142
|
* 读取 UI 文案翻译器——与 useTranslations() 等价,但命名为 get*(**不是 hook**),
|