crelte 0.5.8 → 0.5.10
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/blocks/Blocks.d.ts +41 -4
- package/dist/blocks/Blocks.d.ts.map +1 -1
- package/dist/blocks/Blocks.js +47 -2
- package/dist/blocks/Blocks.svelte +2 -16
- package/dist/blocks/Blocks.svelte.d.ts +20 -37
- package/dist/blocks/Blocks.svelte.d.ts.map +1 -1
- package/dist/blocks/index.d.ts +2 -2
- package/dist/blocks/index.d.ts.map +1 -1
- package/dist/blocks/index.js +2 -2
- package/dist/cookies/ServerCookies.d.ts +1 -1
- package/dist/cookies/ServerCookies.js +1 -1
- package/dist/cookies/index.d.ts +1 -1
- package/dist/crelte.d.ts +21 -10
- package/dist/crelte.d.ts.map +1 -1
- package/dist/index.d.ts +19 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -13
- package/dist/init/client.d.ts +2 -2
- package/dist/init/client.d.ts.map +1 -1
- package/dist/init/client.js +3 -2
- package/dist/init/server.d.ts +7 -6
- package/dist/init/server.d.ts.map +1 -1
- package/dist/init/server.js +6 -6
- package/dist/loadData/Globals.d.ts +3 -3
- package/dist/loadData/Globals.js +3 -3
- package/dist/loadData/loadData.d.ts +8 -18
- package/dist/loadData/loadData.d.ts.map +1 -1
- package/dist/loadData/loadData.js +2 -2
- package/dist/node/index.d.ts +5 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +84 -35
- package/dist/plugins/Events.d.ts +4 -4
- package/dist/plugins/Plugins.d.ts +2 -2
- package/dist/plugins/index.d.ts +2 -3
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +2 -1
- package/dist/queries/Queries.d.ts +9 -0
- package/dist/queries/Queries.d.ts.map +1 -1
- package/dist/queries/Queries.js +29 -17
- package/dist/queries/QueryError.d.ts +2 -0
- package/dist/queries/QueryError.d.ts.map +1 -1
- package/dist/queries/QueryError.js +6 -1
- package/dist/queries/gql.d.ts +2 -2
- package/dist/queries/gql.js +2 -2
- package/dist/queries/index.d.ts +7 -5
- package/dist/queries/index.d.ts.map +1 -1
- package/dist/queries/vars.d.ts +7 -7
- package/dist/queries/vars.d.ts.map +1 -1
- package/dist/queries/vars.js +10 -8
- package/dist/routing/LoadRunner.d.ts +1 -1
- package/dist/routing/LoadRunner.js +1 -1
- package/dist/routing/index.d.ts +4 -4
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/route/BaseRoute.d.ts +25 -25
- package/dist/routing/route/BaseRoute.js +24 -24
- package/dist/routing/route/Request.d.ts +4 -4
- package/dist/routing/route/Request.js +4 -4
- package/dist/routing/route/Route.d.ts +2 -2
- package/dist/routing/route/Route.js +2 -2
- package/dist/routing/router/BaseRouter.d.ts +4 -4
- package/dist/routing/router/BaseRouter.js +3 -3
- package/dist/routing/router/ClientRouter.d.ts +1 -1
- package/dist/routing/router/ClientRouter.js +1 -1
- package/dist/routing/router/Router.d.ts +27 -25
- package/dist/routing/router/Router.d.ts.map +1 -1
- package/dist/routing/router/Router.js +24 -21
- package/dist/routing/router/ServerRouter.d.ts +1 -1
- package/dist/routing/router/ServerRouter.js +1 -1
- package/dist/server/CrelteServer.d.ts +5 -5
- package/dist/server/CrelteServer.d.ts.map +1 -1
- package/dist/server/CrelteServer.js +4 -4
- package/dist/server/Request.d.ts +7 -1
- package/dist/server/Request.d.ts.map +1 -1
- package/dist/server/Request.js +7 -1
- package/dist/server/ServerRouter.d.ts +1 -1
- package/dist/server/ServerRouter.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/shared.d.ts +7 -13
- package/dist/server/shared.d.ts.map +1 -1
- package/dist/server/shared.js +14 -16
- package/dist/ssr/SsrCache.d.ts +56 -4
- package/dist/ssr/SsrCache.d.ts.map +1 -1
- package/dist/ssr/SsrCache.js +94 -20
- package/dist/std/index.d.ts +13 -0
- package/dist/std/index.d.ts.map +1 -1
- package/dist/std/index.js +13 -0
- package/dist/std/rand/index.d.ts +34 -0
- package/dist/std/rand/index.d.ts.map +1 -0
- package/dist/std/rand/index.js +45 -0
- package/dist/std/rand/internal.d.ts +8 -0
- package/dist/std/rand/internal.d.ts.map +1 -0
- package/dist/std/rand/internal.js +15 -0
- package/dist/std/stores/Writable.d.ts +1 -1
- package/dist/std/stores/Writable.js +1 -1
- package/dist/translations/index.d.ts +28 -0
- package/dist/translations/index.d.ts.map +1 -0
- package/dist/translations/index.js +31 -0
- package/dist/translations/loadTranslations.d.ts +23 -0
- package/dist/translations/loadTranslations.d.ts.map +1 -0
- package/dist/translations/loadTranslations.js +26 -0
- package/dist/translations/loader/GlobalLoader.d.ts +34 -0
- package/dist/translations/loader/GlobalLoader.d.ts.map +1 -0
- package/dist/translations/loader/GlobalLoader.js +45 -0
- package/dist/translations/loader/fileLoader/ClientFileLoader.d.ts +10 -0
- package/dist/translations/loader/fileLoader/ClientFileLoader.d.ts.map +1 -0
- package/dist/translations/loader/fileLoader/ClientFileLoader.js +24 -0
- package/dist/translations/loader/fileLoader/NodeFileLoader.d.ts +9 -0
- package/dist/translations/loader/fileLoader/NodeFileLoader.d.ts.map +1 -0
- package/dist/translations/loader/fileLoader/NodeFileLoader.js +21 -0
- package/dist/translations/loader/fileLoader/fileLoader.d.ts +5 -0
- package/dist/translations/loader/fileLoader/fileLoader.d.ts.map +1 -0
- package/dist/translations/loader/fileLoader/fileLoader.js +16 -0
- package/dist/translations/loader/index.d.ts +7 -0
- package/dist/translations/loader/index.d.ts.map +1 -0
- package/dist/translations/loader/index.js +1 -0
- package/dist/translations/translationsPlugin.d.ts +64 -0
- package/dist/translations/translationsPlugin.d.ts.map +1 -0
- package/dist/translations/translationsPlugin.js +110 -0
- package/dist/translations/utils.d.ts +8 -0
- package/dist/translations/utils.d.ts.map +1 -0
- package/dist/translations/utils.js +15 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +30 -38
- package/package.json +9 -1
- package/src/blocks/Blocks.svelte +3 -78
- package/src/blocks/Blocks.ts +63 -6
- package/src/blocks/index.ts +2 -2
- package/src/cookies/ServerCookies.ts +1 -1
- package/src/cookies/index.ts +1 -1
- package/src/crelte.ts +21 -10
- package/src/index.ts +19 -14
- package/src/init/client.ts +3 -2
- package/src/init/server.ts +14 -8
- package/src/loadData/Globals.ts +3 -3
- package/src/loadData/loadData.ts +8 -18
- package/src/node/index.ts +100 -39
- package/src/plugins/Events.ts +4 -4
- package/src/plugins/Plugins.ts +2 -2
- package/src/plugins/index.ts +2 -3
- package/src/queries/Queries.ts +35 -18
- package/src/queries/QueryError.ts +10 -1
- package/src/queries/gql.ts +2 -2
- package/src/queries/index.ts +7 -4
- package/src/queries/vars.ts +12 -8
- package/src/routing/LoadRunner.ts +1 -1
- package/src/routing/index.ts +13 -3
- package/src/routing/route/BaseRoute.ts +25 -25
- package/src/routing/route/Request.ts +4 -4
- package/src/routing/route/Route.ts +2 -2
- package/src/routing/router/BaseRouter.ts +4 -4
- package/src/routing/router/ClientRouter.ts +1 -1
- package/src/routing/router/Router.ts +28 -25
- package/src/routing/router/ServerRouter.ts +1 -1
- package/src/server/CrelteServer.ts +5 -5
- package/src/server/Request.ts +7 -1
- package/src/server/ServerRouter.ts +1 -2
- package/src/server/index.ts +17 -3
- package/src/server/shared.ts +28 -33
- package/src/ssr/SsrCache.ts +104 -22
- package/src/std/index.ts +14 -0
- package/src/std/rand/index.ts +55 -0
- package/src/std/rand/internal.ts +17 -0
- package/src/std/stores/Writable.ts +1 -1
- package/src/translations/index.ts +56 -0
- package/src/translations/loadTranslations.ts +33 -0
- package/src/translations/loader/GlobalLoader.ts +62 -0
- package/src/translations/loader/fileLoader/ClientFileLoader.ts +40 -0
- package/src/translations/loader/fileLoader/NodeFileLoader.ts +32 -0
- package/src/translations/loader/fileLoader/fileLoader.ts +19 -0
- package/src/translations/loader/index.ts +8 -0
- package/src/translations/translationsPlugin.ts +145 -0
- package/src/translations/utils.ts +17 -0
- package/src/vite/index.ts +43 -39
package/src/ssr/SsrCache.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Calculates a key based on the json representation of the data
|
|
3
|
+
*
|
|
4
|
+
* If available hashes the data using SHA-1
|
|
5
|
+
*/
|
|
6
|
+
export async function calcKey(data: any): Promise<string> {
|
|
2
7
|
const json = JSON.stringify(data);
|
|
3
8
|
// this should only happen in an unsecure context
|
|
4
9
|
// specifically in the craft preview locally
|
|
@@ -27,53 +32,130 @@ export async function calcKey(data: any) {
|
|
|
27
32
|
* generally. Storing data and retrieving it will also work on the client.
|
|
28
33
|
*/
|
|
29
34
|
export default class SsrCache {
|
|
30
|
-
private store:
|
|
35
|
+
private store: Map<string, any>;
|
|
31
36
|
|
|
32
37
|
constructor() {
|
|
33
|
-
this.store =
|
|
34
|
-
|
|
35
|
-
// @ts-ignore
|
|
36
|
-
if (typeof window !== 'undefined' && window.SSR_STORE) {
|
|
37
|
-
// @ts-ignore
|
|
38
|
-
this.store = window.SSR_STORE;
|
|
39
|
-
}
|
|
38
|
+
this.store = new Map();
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
42
|
+
* Check if a key exists in the cache
|
|
44
43
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const v = await fn();
|
|
48
|
-
this.set(key, v);
|
|
49
|
-
return v;
|
|
44
|
+
has(key: string): boolean {
|
|
45
|
+
return this.store.has(key);
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
/**
|
|
53
49
|
* Get a value from the cache
|
|
54
50
|
*/
|
|
55
51
|
get<T>(key: string): T | null {
|
|
56
|
-
return this.store
|
|
52
|
+
return this.store.get(key) ?? null;
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
/**
|
|
60
56
|
* Set a value in the cache
|
|
61
57
|
*/
|
|
62
58
|
set<T>(key: string, val: T): T {
|
|
63
|
-
|
|
59
|
+
this.store.set(key, val);
|
|
60
|
+
return val;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* check if the value is in the cache else calls the fn
|
|
65
|
+
*
|
|
66
|
+
* See also {@link getOrInsertLoaded}
|
|
67
|
+
*/
|
|
68
|
+
getOrInsertComputed<T>(key: string, fn: () => T): T {
|
|
69
|
+
if (this.store.has(key)) return this.store.get(key);
|
|
70
|
+
return this.set(key, fn());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* check if the value is in the cache else calls the fn
|
|
75
|
+
*
|
|
76
|
+
* See also {@link getOrInsertComputed}
|
|
77
|
+
*
|
|
78
|
+
* @deprecated use {@link getOrInsertLoaded} instead
|
|
79
|
+
*/
|
|
80
|
+
async load<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
81
|
+
return this.getOrInsertLoaded<T>(key, fn);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* check if the value is in the cache else calls the fn
|
|
86
|
+
*
|
|
87
|
+
* See also {@link getOrInsertComputed}
|
|
88
|
+
*/
|
|
89
|
+
async getOrInsertLoaded<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
90
|
+
if (this.store.has(key)) return this.store.get(key);
|
|
91
|
+
return this.set(key, await fn());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* One-shot SSR handoff value.
|
|
96
|
+
*
|
|
97
|
+
* Intended use: call this once per request (per key) during SSR to generate
|
|
98
|
+
* a value that must match between server render and client hydration.
|
|
99
|
+
*
|
|
100
|
+
* On the server, the value is generated once per key and stored for hydration.
|
|
101
|
+
* Subsequent calls with the same key during SSR currently return the same value,
|
|
102
|
+
* but this behaviour is an implementation detail and may change in the future.
|
|
103
|
+
* Consumers should rely on calling this at most once per key during SSR.
|
|
104
|
+
*
|
|
105
|
+
* On the client, the stored value is returned exactly once and removed.
|
|
106
|
+
* Subsequent calls return a fresh value and are not cached.
|
|
107
|
+
*
|
|
108
|
+
* Warning: this function is designed to be called once per key during SSR.
|
|
109
|
+
* Calling it multiple times may lead to unexpected behaviour if the server-side
|
|
110
|
+
* implementation changes.
|
|
111
|
+
*
|
|
112
|
+
* See also {@link getOrInsertComputed}
|
|
113
|
+
*/
|
|
114
|
+
takeOnce<T>(key: string, fn: () => T): T {
|
|
115
|
+
if (import.meta.env.SSR) {
|
|
116
|
+
if (this.store.has(key)) {
|
|
117
|
+
console.warn(
|
|
118
|
+
`SsrCache.takeOnce called multiple times for key "${key}" during SSR.`,
|
|
119
|
+
);
|
|
120
|
+
return this.store.get(key);
|
|
121
|
+
}
|
|
122
|
+
return this.set(key, fn());
|
|
123
|
+
}
|
|
124
|
+
if (this.store.has(key)) return this.remove<T>(key)!;
|
|
125
|
+
return fn();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Remove a value from the cache and return it
|
|
130
|
+
*/
|
|
131
|
+
remove<T>(key: string): T | null {
|
|
132
|
+
const val = this.get<T>(key);
|
|
133
|
+
this.store.delete(key);
|
|
134
|
+
return val;
|
|
64
135
|
}
|
|
65
136
|
|
|
66
137
|
/** @hidden */
|
|
67
|
-
|
|
68
|
-
this.store
|
|
138
|
+
z_clear() {
|
|
139
|
+
this.store.clear();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @hidden */
|
|
143
|
+
z_importFromHead() {
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
this.store = new Map(window._SSR_STORE ?? []);
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
delete window._SSR_STORE;
|
|
69
148
|
}
|
|
70
149
|
|
|
71
150
|
private exportAsJson(): string {
|
|
72
|
-
return JSON.stringify(this.store).replace(
|
|
151
|
+
return JSON.stringify(Array.from(this.store.entries())).replace(
|
|
152
|
+
/</g,
|
|
153
|
+
'\\u003c',
|
|
154
|
+
);
|
|
73
155
|
}
|
|
74
156
|
|
|
75
157
|
/** @hidden */
|
|
76
|
-
|
|
77
|
-
return `\n\t\t<script>window.
|
|
158
|
+
z_exportToHead(): string {
|
|
159
|
+
return `\n\t\t<script>window._SSR_STORE = ${this.exportAsJson()};</script>`;
|
|
78
160
|
}
|
|
79
161
|
}
|
package/src/std/index.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* Crelte Standard library
|
|
5
|
+
*
|
|
6
|
+
* Provides standard utility functions and types for Crelte applications.
|
|
7
|
+
*
|
|
8
|
+
* ### Modules:
|
|
9
|
+
* - {@link std/intl}
|
|
10
|
+
* - {@link std/stores}
|
|
11
|
+
* - {@link std/sync}
|
|
12
|
+
* - {@link std/rand}
|
|
13
|
+
*/
|
|
14
|
+
|
|
1
15
|
/**
|
|
2
16
|
* Delays for a specified amount of time.
|
|
3
17
|
*
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Crelte } from '../../crelte.js';
|
|
2
|
+
import { getCrelte } from '../../index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a random number between 0 (inclusive) and 1 (exclusive).
|
|
6
|
+
*
|
|
7
|
+
* In SSR mode, the same key will always return the same random number.
|
|
8
|
+
* In client mode, the first call will return the number generated during SSR.
|
|
9
|
+
*/
|
|
10
|
+
export function random(key: string, crelte: Crelte = getCrelte()): number {
|
|
11
|
+
return crelte.ssrCache.takeOnce('_rand_' + key, () => Math.random());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a deterministic random number generator.
|
|
16
|
+
*
|
|
17
|
+
* Intended use: create this once during component initialization when a sequence
|
|
18
|
+
* of random values must be consistent between server-side rendering and client
|
|
19
|
+
* hydration.
|
|
20
|
+
*
|
|
21
|
+
* The generator is seeded during SSR using the provided key. During hydration,
|
|
22
|
+
* the same seed is reused so that the client produces the same sequence of random
|
|
23
|
+
* numbers as the server for the initial render.
|
|
24
|
+
*
|
|
25
|
+
* After hydration, the generator continues independently on the client.
|
|
26
|
+
*
|
|
27
|
+
* @returns A function that returns a pseudo-random number in the range
|
|
28
|
+
* [0, 1).
|
|
29
|
+
*
|
|
30
|
+
* #### Example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const rng = createRng('lucky-number');
|
|
33
|
+
*
|
|
34
|
+
* const val1 = rng(); // same on server and client
|
|
35
|
+
* const val2 = rng(); // same on server and client
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function createRng(
|
|
39
|
+
key: string,
|
|
40
|
+
crelte: Crelte = getCrelte(),
|
|
41
|
+
): () => number {
|
|
42
|
+
const seed = crelte.ssrCache.takeOnce('_rng_' + key, () => Math.random());
|
|
43
|
+
|
|
44
|
+
// park miller algorithm
|
|
45
|
+
|
|
46
|
+
const MOD = 2147483647;
|
|
47
|
+
const MUL = 16807;
|
|
48
|
+
|
|
49
|
+
let state = Math.floor(seed * (MOD - 1)) + 1;
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
state = (state * MUL) % MOD;
|
|
53
|
+
return (state - 1) / (MOD - 1);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const ALPHABET: string =
|
|
2
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
3
|
+
const ALPHABET_LENGTH = ALPHABET.length;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a random token of a specified length.
|
|
7
|
+
*
|
|
8
|
+
* @param length - The desired length of the token.
|
|
9
|
+
* @returns A random token.
|
|
10
|
+
*/
|
|
11
|
+
export function randomToken(length: number = 8): string {
|
|
12
|
+
let s = '';
|
|
13
|
+
for (let i = 0; i < length; i++) {
|
|
14
|
+
s += ALPHABET[Math.floor(Math.random() * ALPHABET_LENGTH)];
|
|
15
|
+
}
|
|
16
|
+
return s;
|
|
17
|
+
}
|
|
@@ -27,7 +27,7 @@ export default class Writable<T> {
|
|
|
27
27
|
* The function get's called once with the current value and then when the
|
|
28
28
|
* values changes
|
|
29
29
|
*
|
|
30
|
-
*
|
|
30
|
+
* #### Note
|
|
31
31
|
* This does not check for equality like svelte.
|
|
32
32
|
*
|
|
33
33
|
* @return a function which should be called to unsubscribe
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* ## Unstable
|
|
5
|
+
* The translations module is not yet stable. APIs may change without a major version bump.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createFileLoader } from './loader/fileLoader/fileLoader.js';
|
|
9
|
+
import { createGlobalLoader } from './loader/GlobalLoader.js';
|
|
10
|
+
import { type TranslationsLoader, type LoaderCreator } from './loader/index.js';
|
|
11
|
+
import loadTranslations from './loadTranslations.js';
|
|
12
|
+
import {
|
|
13
|
+
createTranslations,
|
|
14
|
+
getTranslationsPlugin,
|
|
15
|
+
type TranslateFunction,
|
|
16
|
+
type TranslateStore,
|
|
17
|
+
type Translations,
|
|
18
|
+
TranslationsPlugin,
|
|
19
|
+
type TranslationsPluginOptions,
|
|
20
|
+
} from './translationsPlugin.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a translate store for the given namespace.
|
|
24
|
+
*
|
|
25
|
+
* #### Example
|
|
26
|
+
* ```svelte
|
|
27
|
+
* <script>
|
|
28
|
+
* import { getTranslations } from 'crelte/translations';
|
|
29
|
+
*
|
|
30
|
+
* const t = getTranslations();
|
|
31
|
+
* </script>
|
|
32
|
+
*
|
|
33
|
+
* <h1>{$t('welcome_message')}</h1>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function getTranslations(namespace: string = 'common'): TranslateStore {
|
|
37
|
+
const plugin = getTranslationsPlugin();
|
|
38
|
+
return plugin.z_createTranslateStore(namespace);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
createTranslations,
|
|
43
|
+
TranslationsPluginOptions,
|
|
44
|
+
loadTranslations,
|
|
45
|
+
getTranslations,
|
|
46
|
+
getTranslationsPlugin,
|
|
47
|
+
Translations,
|
|
48
|
+
TranslationsPlugin,
|
|
49
|
+
TranslationsLoader,
|
|
50
|
+
LoaderCreator,
|
|
51
|
+
TranslateStore,
|
|
52
|
+
TranslateFunction,
|
|
53
|
+
// loaders
|
|
54
|
+
createFileLoader,
|
|
55
|
+
createGlobalLoader,
|
|
56
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CrelteRequest } from '../crelte.js';
|
|
2
|
+
import { getTranslationsPlugin, TranslateStore } from './translationsPlugin.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a translate store and loads the specified namespace.
|
|
6
|
+
*
|
|
7
|
+
* #### Example
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script module>
|
|
10
|
+
* import { loadTranslations } from 'crelte/translations';
|
|
11
|
+
* export const loadData = {
|
|
12
|
+
* t: cr => loadTranslations(cr, 'customNamespace')
|
|
13
|
+
* };
|
|
14
|
+
* </script>
|
|
15
|
+
*
|
|
16
|
+
* <script>
|
|
17
|
+
* let { t } = $props();
|
|
18
|
+
* </script>
|
|
19
|
+
*
|
|
20
|
+
* <h1>{$t('welcome_message')}</h1>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export default async function loadTranslations(
|
|
24
|
+
cr: CrelteRequest,
|
|
25
|
+
namespace: string,
|
|
26
|
+
): Promise<TranslateStore> {
|
|
27
|
+
const plugin = getTranslationsPlugin(cr);
|
|
28
|
+
|
|
29
|
+
// we don't need the return value here as it's cached
|
|
30
|
+
await plugin.load(cr, namespace);
|
|
31
|
+
|
|
32
|
+
return plugin.z_createTranslateStore(namespace);
|
|
33
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Translations } from '../translationsPlugin.js';
|
|
2
|
+
import { Crelte, CrelteRequest } from '../../crelte.js';
|
|
3
|
+
import { LoaderCreator, TranslationsLoader } from '../index.js';
|
|
4
|
+
|
|
5
|
+
export default class GlobalLoader implements TranslationsLoader {
|
|
6
|
+
private handle: string;
|
|
7
|
+
|
|
8
|
+
constructor(_crelte: Crelte, opts: { handle?: string }) {
|
|
9
|
+
this.handle = opts.handle ?? 'translations';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async load(cr: CrelteRequest, namespace: string): Promise<Translations> {
|
|
13
|
+
const globalSet = await cr.globals.getAsync(this.handle);
|
|
14
|
+
if (!globalSet || typeof globalSet !== 'object')
|
|
15
|
+
throw new Error(`missing globals \`${this.handle}\``);
|
|
16
|
+
|
|
17
|
+
const data = globalSet[namespace];
|
|
18
|
+
if (!data)
|
|
19
|
+
throw new Error(
|
|
20
|
+
`could not find \`${namespace}\` in globals \`${this.handle}\``,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (typeof data !== 'string') return data;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`could not parse \`${this.handle}.${namespace}\` as json \n\n` +
|
|
30
|
+
(e instanceof Error ? e.message : ''),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a loader that loads translations from a global set.
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { createTranslations, createGlobalLoader } from 'crelte/translations';
|
|
41
|
+
*
|
|
42
|
+
* createTranslations({ loader: createGlobalLoader() });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Then in you're global export a `translations` globalSet with namespaces
|
|
46
|
+
* as fields, at least `common`.
|
|
47
|
+
*
|
|
48
|
+
* ```graphql
|
|
49
|
+
* translations: globalSet(handle: "translations", siteId: $siteId) {
|
|
50
|
+
* ... on translations_GlobalSet {
|
|
51
|
+
* common
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
*/
|
|
55
|
+
export function createGlobalLoader(
|
|
56
|
+
opts: {
|
|
57
|
+
/** the handle for the global set containing the namespaces (default = translations) */
|
|
58
|
+
handle?: string;
|
|
59
|
+
} = {},
|
|
60
|
+
): LoaderCreator {
|
|
61
|
+
return crelte => new GlobalLoader(crelte, opts);
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CrelteRequest } from '../../../crelte.js';
|
|
2
|
+
import SsrCache from '../../../ssr/SsrCache.js';
|
|
3
|
+
import { Translations } from '../../translationsPlugin.js';
|
|
4
|
+
import { TranslationsLoader } from '../index.js';
|
|
5
|
+
import { SSR_RUN_NUMBER_KEY, translationFilePath } from './fileLoader.js';
|
|
6
|
+
|
|
7
|
+
export default class ClientFileLoader implements TranslationsLoader {
|
|
8
|
+
private readonly runNumber: string;
|
|
9
|
+
|
|
10
|
+
constructor(cache: SsrCache) {
|
|
11
|
+
this.runNumber = cache.get(SSR_RUN_NUMBER_KEY) ?? '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async load(cr: CrelteRequest, namespace: string): Promise<Translations> {
|
|
15
|
+
const lang = cr.site.language;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const resp = await fetch(
|
|
19
|
+
`${translationFilePath(lang, namespace)}?run=${this.runNumber}`,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (!resp.ok) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Failed to fetch translations file for ${lang}/${namespace}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// todo: validate data
|
|
29
|
+
const data: Translations = await resp.json();
|
|
30
|
+
|
|
31
|
+
return data;
|
|
32
|
+
} catch (e: unknown) {
|
|
33
|
+
console.error(e);
|
|
34
|
+
throw new Error(
|
|
35
|
+
'There is something wrong with your translations file. \n\n' +
|
|
36
|
+
(e instanceof Error ? e.message : ''),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { SSR_RUN_NUMBER_KEY, translationFilePath } from './fileLoader.js';
|
|
3
|
+
import { randomToken } from '../../utils.js';
|
|
4
|
+
import { Translations } from '../../translationsPlugin.js';
|
|
5
|
+
import SsrCache from '../../../ssr/SsrCache.js';
|
|
6
|
+
import { CrelteRequest } from '../../../crelte.js';
|
|
7
|
+
import { TranslationsLoader } from '../index.js';
|
|
8
|
+
|
|
9
|
+
export default class NodeFileLoader implements TranslationsLoader {
|
|
10
|
+
constructor(cache: SsrCache) {
|
|
11
|
+
// store RUN_NUMBER in ssr store
|
|
12
|
+
cache.set(SSR_RUN_NUMBER_KEY, randomToken());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async load(cr: CrelteRequest, namespace: string): Promise<Translations> {
|
|
16
|
+
const lang = cr.site.language;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const fileString = await fs.readFile(
|
|
20
|
+
`./public${translationFilePath(lang, namespace)}`,
|
|
21
|
+
'utf-8',
|
|
22
|
+
);
|
|
23
|
+
// todo: validate data
|
|
24
|
+
return JSON.parse(fileString) as Translations;
|
|
25
|
+
} catch (e: unknown) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'There is something wrong with your translations file. \n\n' +
|
|
28
|
+
(e instanceof Error ? e.message : ''),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LoaderCreator } from '../index.js';
|
|
2
|
+
import ClientFileLoader from './ClientFileLoader.js';
|
|
3
|
+
import NodeFileLoader from './NodeFileLoader.js';
|
|
4
|
+
|
|
5
|
+
export const SSR_RUN_NUMBER_KEY = 'st-run-number';
|
|
6
|
+
|
|
7
|
+
/// Returns a path to the translations file for the given language and namespace.
|
|
8
|
+
/// Always prefixed with a slash.
|
|
9
|
+
export function translationFilePath(lang: string, namespace: string): string {
|
|
10
|
+
return `/translations/${lang}/${namespace}.json`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createFileLoader(): LoaderCreator {
|
|
14
|
+
if (import.meta.env.SSR) {
|
|
15
|
+
return crelte => new NodeFileLoader(crelte.ssrCache);
|
|
16
|
+
} else {
|
|
17
|
+
return crelte => new ClientFileLoader(crelte.ssrCache);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Translations } from '../translationsPlugin.js';
|
|
2
|
+
import { Crelte, CrelteRequest } from '../../crelte.js';
|
|
3
|
+
|
|
4
|
+
export interface TranslationsLoader {
|
|
5
|
+
load(cr: CrelteRequest, namespace: string): Promise<Translations>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type LoaderCreator = (crelte: Crelte) => TranslationsLoader;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { derived } from 'svelte/store';
|
|
2
|
+
import { LoaderCreator, TranslationsLoader } from './loader/index.js';
|
|
3
|
+
import { Plugin, PluginCreator } from '../plugins/Plugins.js';
|
|
4
|
+
import { Crelte, CrelteRequest } from '../crelte.js';
|
|
5
|
+
import Readable from '../std/stores/Readable.js';
|
|
6
|
+
import { getCrelte } from '../index.js';
|
|
7
|
+
|
|
8
|
+
export type TranslateFunction = (
|
|
9
|
+
key: string,
|
|
10
|
+
// replacements?: Record<string, string>,
|
|
11
|
+
) => string;
|
|
12
|
+
export type TranslateStore = Readable<TranslateFunction>;
|
|
13
|
+
export type Translations = Record<string, string>;
|
|
14
|
+
export type TranslationsPluginOptions = {
|
|
15
|
+
/** use either `createFileLoader()` or `createGlobalLoader()` */
|
|
16
|
+
loader: LoaderCreator;
|
|
17
|
+
/** The common namespace is always loaded */
|
|
18
|
+
loadNamespaces?: string[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const SSR_KEY = '_translations';
|
|
22
|
+
|
|
23
|
+
export class TranslationsPlugin implements Plugin {
|
|
24
|
+
private crelte: Crelte;
|
|
25
|
+
/// Map<`lang-namespace`, Translations>
|
|
26
|
+
private translations: Map<string, Translations>;
|
|
27
|
+
private loader: TranslationsLoader;
|
|
28
|
+
/// When the derived of site is used in the first loadData site is not defined
|
|
29
|
+
/// so we need to store the first language here
|
|
30
|
+
private firstLang: string | null;
|
|
31
|
+
|
|
32
|
+
get name(): string {
|
|
33
|
+
return 'translations';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(crelte: Crelte, opts: TranslationsPluginOptions) {
|
|
37
|
+
this.crelte = crelte;
|
|
38
|
+
this.translations = new Map(crelte.ssrCache.get(SSR_KEY));
|
|
39
|
+
this.firstLang = null;
|
|
40
|
+
|
|
41
|
+
if (import.meta.env.DEV && typeof opts?.loader !== 'function') {
|
|
42
|
+
throw new Error(
|
|
43
|
+
'TranslationsPlugin requires a loader, use `createFileLoader()` or ' +
|
|
44
|
+
'`createGlobalLoader()`',
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.loader = opts.loader(crelte);
|
|
49
|
+
const loadNamespaces = opts.loadNamespaces ?? [];
|
|
50
|
+
|
|
51
|
+
// preload default translations
|
|
52
|
+
crelte.events.on('loadGlobalData', async (cr: CrelteRequest) => {
|
|
53
|
+
this.firstLang = cr.site.language;
|
|
54
|
+
await Promise.all([
|
|
55
|
+
this.load(cr, 'common'),
|
|
56
|
+
...loadNamespaces.map(ns => this.load(cr, ns)),
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* load needs to take a CrelteRequest to be able to get the correct site
|
|
63
|
+
* and globals
|
|
64
|
+
*
|
|
65
|
+
* in crelte 0.5 this should also be possible without a CrelteRequest
|
|
66
|
+
* but with multiple plugin instances / Request State plugins
|
|
67
|
+
*/
|
|
68
|
+
async load(cr: CrelteRequest, namespace: string): Promise<Translations> {
|
|
69
|
+
const lang = cr.site.language;
|
|
70
|
+
const key = `${lang}-${namespace}`;
|
|
71
|
+
const tData = this.translations.get(key);
|
|
72
|
+
if (tData) return tData;
|
|
73
|
+
|
|
74
|
+
const data = await this.loader.load(cr, namespace);
|
|
75
|
+
this.translations.set(key, data);
|
|
76
|
+
if (import.meta.env.SSR) {
|
|
77
|
+
// only store the translations in the ssrCache during ssr
|
|
78
|
+
// since after that we wont read it again
|
|
79
|
+
cr.ssrCache.set(SSR_KEY, Array.from(this.translations.entries()));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get(lang: string, namespace: string): Translations | null {
|
|
86
|
+
return this.translations.get(`${lang}-${namespace}`) ?? null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @hidden */
|
|
90
|
+
z_createTranslateStore(namespace: string): TranslateStore {
|
|
91
|
+
const store = derived(this.crelte.router.site, site => {
|
|
92
|
+
return (key: string) => {
|
|
93
|
+
const lang = site?.language ?? this.firstLang;
|
|
94
|
+
if (!lang) throw new Error('no lang');
|
|
95
|
+
|
|
96
|
+
const data = this.get(lang, namespace);
|
|
97
|
+
if (!data) console.error(`namespace '${namespace}' not loaded`);
|
|
98
|
+
return data?.[key] || key;
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return new Readable(store);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create the TranslationsPlugin
|
|
108
|
+
*
|
|
109
|
+
* #### Setup file loader
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { createTranslations, createFileLoader } from 'crelte/translations';
|
|
113
|
+
*
|
|
114
|
+
* createTranslations({ loader: createFileLoader() });
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* #### Setup global loader
|
|
118
|
+
* ```ts
|
|
119
|
+
* import { createTranslations, createGlobalLoader } from 'crelte/translations';
|
|
120
|
+
*
|
|
121
|
+
* createTranslations({ loader: createGlobalLoader() });
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* Then in you're global export a `translations` globalSet with namespaces
|
|
125
|
+
* as fields, at least `common`.
|
|
126
|
+
*
|
|
127
|
+
* ```graphql
|
|
128
|
+
* translations: globalSet(handle: "translations", siteId: $siteId) {
|
|
129
|
+
* ... on translations_GlobalSet {
|
|
130
|
+
* common
|
|
131
|
+
* }
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function createTranslations(
|
|
136
|
+
opts: TranslationsPluginOptions,
|
|
137
|
+
): PluginCreator {
|
|
138
|
+
return crelte => new TranslationsPlugin(crelte, opts);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getTranslationsPlugin(
|
|
142
|
+
crelte: Crelte = getCrelte(),
|
|
143
|
+
): TranslationsPlugin {
|
|
144
|
+
return crelte.getPlugin('translations') as TranslationsPlugin;
|
|
145
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const ALPHABET: string =
|
|
2
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
3
|
+
const ALPHABET_LENGTH = ALPHABET.length;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a random token of a specified length.
|
|
7
|
+
*
|
|
8
|
+
* @param length - The desired length of the token.
|
|
9
|
+
* @returns A random token.
|
|
10
|
+
*/
|
|
11
|
+
export function randomToken(length: number = 8): string {
|
|
12
|
+
let s = '';
|
|
13
|
+
for (let i = 0; i < length; i++) {
|
|
14
|
+
s += ALPHABET[Math.floor(Math.random() * ALPHABET_LENGTH)];
|
|
15
|
+
}
|
|
16
|
+
return s;
|
|
17
|
+
}
|