@aidc-toolkit/core 1.0.31-beta → 1.0.33-beta
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 +98 -87
- package/dist/app-data-storage.d.ts +118 -0
- package/dist/app-data-storage.d.ts.map +1 -0
- package/dist/app-data-storage.js +117 -0
- package/dist/app-data-storage.js.map +1 -0
- package/dist/app-data.d.ts +26 -0
- package/dist/app-data.d.ts.map +1 -0
- package/dist/app-data.js +79 -0
- package/dist/app-data.js.map +1 -0
- package/dist/browser-app-data-storage.d.ts +26 -0
- package/dist/browser-app-data-storage.d.ts.map +1 -0
- package/dist/browser-app-data-storage.js +34 -0
- package/dist/browser-app-data-storage.js.map +1 -0
- package/dist/cache.d.ts +58 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +12 -0
- package/dist/cache.js.map +1 -0
- package/dist/file-app-data-storage.d.ts +26 -0
- package/dist/file-app-data-storage.d.ts.map +1 -0
- package/dist/file-app-data-storage.js +40 -0
- package/dist/file-app-data-storage.js.map +1 -0
- package/dist/hyperlink.d.ts +18 -0
- package/dist/hyperlink.d.ts.map +1 -0
- package/dist/hyperlink.js +2 -0
- package/dist/hyperlink.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/local-app-data-storage.d.ts +8 -0
- package/dist/local-app-data-storage.d.ts.map +1 -0
- package/dist/local-app-data-storage.js +11 -0
- package/dist/local-app-data-storage.js.map +1 -0
- package/dist/locale/en/locale-resources.d.ts +10 -0
- package/dist/locale/en/locale-resources.d.ts.map +1 -0
- package/dist/locale/en/locale-resources.js +9 -0
- package/dist/locale/en/locale-resources.js.map +1 -0
- package/dist/locale/fr/locale-resources.d.ts +10 -0
- package/dist/locale/fr/locale-resources.d.ts.map +1 -0
- package/dist/locale/fr/locale-resources.js +9 -0
- package/dist/locale/fr/locale-resources.js.map +1 -0
- package/dist/locale/i18n.d.ts +33 -6
- package/dist/locale/i18n.d.ts.map +1 -1
- package/dist/locale/i18n.js +76 -31
- package/dist/locale/i18n.js.map +1 -1
- package/dist/logger.d.ts +108 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +227 -12
- package/dist/logger.js.map +1 -1
- package/dist/remote-app-data-storage.d.ts +18 -0
- package/dist/remote-app-data-storage.d.ts.map +1 -0
- package/dist/remote-app-data-storage.js +37 -0
- package/dist/remote-app-data-storage.js.map +1 -0
- package/dist/type-helper.js.map +1 -1
- package/dist/type.d.ts +5 -0
- package/dist/type.d.ts.map +1 -1
- package/package.json +10 -8
- package/src/app-data-storage.ts +166 -0
- package/src/app-data.ts +94 -0
- package/src/browser-app-data-storage.ts +37 -0
- package/src/cache.ts +64 -0
- package/src/file-app-data-storage.ts +49 -0
- package/src/hyperlink.ts +19 -0
- package/src/index.ts +10 -0
- package/src/local-app-data-storage.ts +12 -0
- package/src/locale/en/locale-resources.ts +8 -0
- package/src/locale/fr/locale-resources.ts +8 -0
- package/src/locale/i18n.ts +102 -36
- package/src/locale/i18next.d.ts +16 -0
- package/src/logger.ts +250 -13
- package/src/remote-app-data-storage.ts +38 -0
- package/src/type-helper.ts +2 -2
- package/src/type.ts +6 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type AppData, decodeAppData, encodeAppData } from "./app-data.js";
|
|
2
|
+
import type { Promisable } from "./type.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generic read-only application data storage.
|
|
6
|
+
*/
|
|
7
|
+
export abstract class ReadOnlyAppDataStorage<SupportsBinary extends boolean> {
|
|
8
|
+
/**
|
|
9
|
+
* Extension to identify binary data.
|
|
10
|
+
*/
|
|
11
|
+
protected static readonly BINARY_EXTENSION = ".bin";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extension to identify JSON data.
|
|
15
|
+
*/
|
|
16
|
+
protected static readonly JSON_EXTENSION = ".json";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* True if binary data is supported natively.
|
|
20
|
+
*/
|
|
21
|
+
readonly #supportsBinary: SupportsBinary;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Storage path prepended to each key.
|
|
25
|
+
*/
|
|
26
|
+
readonly #path: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Constructor.
|
|
30
|
+
*
|
|
31
|
+
* @param supportsBinary
|
|
32
|
+
* True if binary data is supported.
|
|
33
|
+
*
|
|
34
|
+
* @param path
|
|
35
|
+
* Storage path prepended to each key along with '/' if defined, empty string if not.
|
|
36
|
+
*/
|
|
37
|
+
protected constructor(supportsBinary: SupportsBinary, path?: string) {
|
|
38
|
+
this.#supportsBinary = supportsBinary;
|
|
39
|
+
this.#path = path !== undefined ? `${path}/` : "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Determine if binary data is supported.
|
|
44
|
+
*/
|
|
45
|
+
get supportsBinary(): SupportsBinary {
|
|
46
|
+
return this.#supportsBinary;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the storage path, prepended to each key.
|
|
51
|
+
*/
|
|
52
|
+
get path(): string {
|
|
53
|
+
return this.#path;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the full storage key.
|
|
58
|
+
*
|
|
59
|
+
* @param pathKey
|
|
60
|
+
* Key relative to path.
|
|
61
|
+
*
|
|
62
|
+
* @param isBinary
|
|
63
|
+
* True if key is to binary data, false or undefined if to string data. Ignored if binary data is not supported.
|
|
64
|
+
*
|
|
65
|
+
* @returns
|
|
66
|
+
* Full storage key.
|
|
67
|
+
*/
|
|
68
|
+
protected fullKey(pathKey: string, isBinary: boolean): string {
|
|
69
|
+
const keyNoExtension = `${this.path}${pathKey}`;
|
|
70
|
+
|
|
71
|
+
// Add extension to key if binary data is supported.
|
|
72
|
+
return this.supportsBinary ?
|
|
73
|
+
`${keyNoExtension}${isBinary ? ReadOnlyAppDataStorage.BINARY_EXTENSION : ReadOnlyAppDataStorage.JSON_EXTENSION}` :
|
|
74
|
+
keyNoExtension;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Read a string or binary data from persistent storage.
|
|
79
|
+
*
|
|
80
|
+
* @param key
|
|
81
|
+
* Storage key (file path in Node.js, key in localStorage).
|
|
82
|
+
*
|
|
83
|
+
* @param asBinary
|
|
84
|
+
* True if binary data is requested, false or undefined if string data is requested. Ignored if binary data is not
|
|
85
|
+
* supported.
|
|
86
|
+
*
|
|
87
|
+
* @returns
|
|
88
|
+
* String or binary data or undefined if not found.
|
|
89
|
+
*/
|
|
90
|
+
protected abstract doRead(key: string, asBinary: boolean | undefined): Promisable<(SupportsBinary extends true ? string | Uint8Array : string) | undefined>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Read application data from storage.
|
|
94
|
+
*
|
|
95
|
+
* @param pathKey
|
|
96
|
+
* Key relative to path.
|
|
97
|
+
*
|
|
98
|
+
* @param asBinary
|
|
99
|
+
* True if binary data is requested, false or undefined if string data is requested. Ignored if binary data is not
|
|
100
|
+
* supported.
|
|
101
|
+
*
|
|
102
|
+
* @returns
|
|
103
|
+
* Application data or undefined if not found.
|
|
104
|
+
*/
|
|
105
|
+
async read(pathKey: string, asBinary?: boolean): Promise<AppData | undefined> {
|
|
106
|
+
const data = await this.doRead(this.fullKey(pathKey, asBinary === true), asBinary);
|
|
107
|
+
|
|
108
|
+
return typeof data === "string" ? decodeAppData(data) : data;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generic read/write application data storage.
|
|
114
|
+
*/
|
|
115
|
+
export abstract class AppDataStorage<SupportsBinary extends boolean> extends ReadOnlyAppDataStorage<SupportsBinary> {
|
|
116
|
+
/**
|
|
117
|
+
* Write a string or binary data in persistent storage.
|
|
118
|
+
*
|
|
119
|
+
* @param key
|
|
120
|
+
* Storage key (file path in Node.js, key in localStorage).
|
|
121
|
+
*
|
|
122
|
+
* @param data
|
|
123
|
+
* String or binary data.
|
|
124
|
+
*/
|
|
125
|
+
protected abstract doWrite(key: string, data: SupportsBinary extends true ? string | Uint8Array : string): Promisable<void>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Write application data to storage.
|
|
129
|
+
*
|
|
130
|
+
* @param pathKey
|
|
131
|
+
* Key relative to path.
|
|
132
|
+
*
|
|
133
|
+
* @param appData
|
|
134
|
+
* Application data to write.
|
|
135
|
+
*/
|
|
136
|
+
async write(pathKey: string, appData: AppData): Promise<void> {
|
|
137
|
+
const isBinary = appData instanceof Uint8Array;
|
|
138
|
+
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Type is determined by supports binary flag.
|
|
140
|
+
await this.doWrite(this.fullKey(pathKey, isBinary), (this.supportsBinary && isBinary ?
|
|
141
|
+
appData :
|
|
142
|
+
encodeAppData(appData)
|
|
143
|
+
) as Parameters<typeof this.doWrite>[1]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Delete from persistent storage.
|
|
148
|
+
*
|
|
149
|
+
* @param key
|
|
150
|
+
* Storage key (file path in Node.js, key in localStorage).
|
|
151
|
+
*/
|
|
152
|
+
protected abstract doDelete(key: string): Promisable<void>;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Delete application data from persistent storage.
|
|
156
|
+
*
|
|
157
|
+
* @param pathKey
|
|
158
|
+
* Key relative to path.
|
|
159
|
+
*
|
|
160
|
+
* @param asBinary
|
|
161
|
+
* True if key is to binary data, false or undefined if to string data. Ignored if binary data is not supported.
|
|
162
|
+
*/
|
|
163
|
+
async delete(pathKey: string, asBinary?: boolean): Promise<void> {
|
|
164
|
+
await this.doDelete(this.fullKey(pathKey, asBinary === true));
|
|
165
|
+
}
|
|
166
|
+
}
|
package/src/app-data.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { fromByteArray, toByteArray } from "base64-js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Application data.
|
|
5
|
+
*/
|
|
6
|
+
export type AppData = string | number | boolean | object;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Decode application data from an encoded string.
|
|
10
|
+
*
|
|
11
|
+
* @param stringData
|
|
12
|
+
* String data.
|
|
13
|
+
*
|
|
14
|
+
* @returns
|
|
15
|
+
* Decoded application data.
|
|
16
|
+
*/
|
|
17
|
+
export function decodeAppData(stringData: string): AppData | undefined {
|
|
18
|
+
let decodedAppData: AppData | undefined;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Mapping is expected to be correct.
|
|
22
|
+
decodedAppData = JSON.parse(stringData, (_key, value: unknown) => {
|
|
23
|
+
let replacementValue = value;
|
|
24
|
+
|
|
25
|
+
// Decode string representing date/time and binary array and pass through other values unmodified.
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
// First capture group is type, second is data; simple split at ':' character.
|
|
28
|
+
const stringDataGroups = /^(?<type>\w+):(?<data>.*)$/u.exec(value)?.groups;
|
|
29
|
+
|
|
30
|
+
if (stringDataGroups !== undefined) {
|
|
31
|
+
const type = stringDataGroups["type"];
|
|
32
|
+
const data = stringDataGroups["data"];
|
|
33
|
+
|
|
34
|
+
switch (type) {
|
|
35
|
+
case "dateTime":
|
|
36
|
+
replacementValue = new Date(data);
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case "binary":
|
|
40
|
+
replacementValue = toByteArray(data);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return replacementValue;
|
|
47
|
+
}) as AppData;
|
|
48
|
+
} catch {
|
|
49
|
+
// String data is not valid JSON; discard it.
|
|
50
|
+
decodedAppData = undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return decodedAppData;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Encode an object to a format suitable for storage.
|
|
58
|
+
*
|
|
59
|
+
* @param o
|
|
60
|
+
* Object.
|
|
61
|
+
*
|
|
62
|
+
* @returns
|
|
63
|
+
* Object suitable for storage with date/time and binary types encoded as strings.
|
|
64
|
+
*/
|
|
65
|
+
function encodeObject(o: object): object | string {
|
|
66
|
+
let mappedData: object | string;
|
|
67
|
+
|
|
68
|
+
// Encode date/time and binary array as string and pass through other values unmodified.
|
|
69
|
+
if (o instanceof Date) {
|
|
70
|
+
mappedData = `dateTime:${o.toISOString()}`;
|
|
71
|
+
} else if (o instanceof Uint8Array) {
|
|
72
|
+
mappedData = `binary:${fromByteArray(o)}`;
|
|
73
|
+
} else {
|
|
74
|
+
mappedData = Object.fromEntries(Object.entries(o).map(([key, value]: [string, unknown]) =>
|
|
75
|
+
[key, typeof value === "object" && value !== null ? encodeObject(value) : value]
|
|
76
|
+
));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return mappedData;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Encode application data as a string for storage. Encoded string is in JSON format with date/time and binary data
|
|
84
|
+
* converted to identifiable strings for decoding.
|
|
85
|
+
*
|
|
86
|
+
* @param appData
|
|
87
|
+
* Application data.
|
|
88
|
+
*
|
|
89
|
+
* @returns
|
|
90
|
+
* Encoded application data.
|
|
91
|
+
*/
|
|
92
|
+
export function encodeAppData(appData: AppData): string {
|
|
93
|
+
return JSON.stringify(typeof appData !== "object" ? appData : encodeObject(appData));
|
|
94
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AppDataStorage } from "./app-data-storage.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Application data storage using the browser local storage.
|
|
5
|
+
*/
|
|
6
|
+
export class BrowserAppDataStorage extends AppDataStorage<false> {
|
|
7
|
+
/**
|
|
8
|
+
* Constructor.
|
|
9
|
+
*
|
|
10
|
+
* @param path
|
|
11
|
+
* Storage path prepended to each key along with '/' if defined, empty string if not.
|
|
12
|
+
*/
|
|
13
|
+
constructor(path?: string) {
|
|
14
|
+
super(false, path);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @inheritDoc
|
|
19
|
+
*/
|
|
20
|
+
protected override doRead(key: string): string | undefined {
|
|
21
|
+
return localStorage.getItem(key) ?? undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @inheritDoc
|
|
26
|
+
*/
|
|
27
|
+
protected override doWrite(key: string, s: string): void {
|
|
28
|
+
localStorage.setItem(key, s);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @inheritDoc
|
|
33
|
+
*/
|
|
34
|
+
protected override doDelete(key: string): void {
|
|
35
|
+
localStorage.removeItem(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Promisable } from "./type.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic cache. Typically used to manage a local copy of remote data that is not refreshed regularly.
|
|
5
|
+
*
|
|
6
|
+
* @template TCache
|
|
7
|
+
* Type of cached data.
|
|
8
|
+
*
|
|
9
|
+
* @template TSource
|
|
10
|
+
* Type of source data. The type may be different from the cached data type if a transformation is required.
|
|
11
|
+
*/
|
|
12
|
+
export abstract class Cache<TCache, TSource = TCache> {
|
|
13
|
+
/**
|
|
14
|
+
* Get the date/time at or after which the source should be checked for updates. If the value is undefined, this is
|
|
15
|
+
* the first usage.
|
|
16
|
+
*/
|
|
17
|
+
abstract get nextCheckDateTime(): Promisable<Date | undefined>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the date/time at which the cache was last updated. This may more accurately reflect the date/time at which
|
|
21
|
+
* the last source retrieved was updated. If the value is undefined, there is no data in the cache.
|
|
22
|
+
*/
|
|
23
|
+
abstract get cacheDateTime(): Promisable<Date | undefined>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the cache data. This should only ever be called if the cache date/time is defined.
|
|
27
|
+
*/
|
|
28
|
+
abstract get cacheData(): Promisable<TCache>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the date/time at which the source was last updated. This should not be called unless the next check date/time
|
|
32
|
+
* has passed, as it may trigger an expensive remote retrieval.
|
|
33
|
+
*/
|
|
34
|
+
abstract get sourceDateTime(): Promisable<Date>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the source data. This should not be called unless the next check date/time has passed, as it may trigger an
|
|
38
|
+
* expensive remote retrieval.
|
|
39
|
+
*/
|
|
40
|
+
abstract get sourceData(): Promisable<TSource>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Update the cache with only the next check date/time. The cache date/time and cache data must not be modified.
|
|
44
|
+
* This is typically called when the cache is up to date with the source or source retrieval has failed.
|
|
45
|
+
*
|
|
46
|
+
* @param nextCheckDateTime
|
|
47
|
+
* Next check date/time.
|
|
48
|
+
*/
|
|
49
|
+
abstract update(nextCheckDateTime: Date): Promisable<void>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Update all cache parameters. This is typically called when the cache is updated from the source.
|
|
53
|
+
*
|
|
54
|
+
* @param nextCheckDateTime
|
|
55
|
+
* Next check date/time.
|
|
56
|
+
*
|
|
57
|
+
* @param cacheDateTime
|
|
58
|
+
* Cache date/time.
|
|
59
|
+
*
|
|
60
|
+
* @param cacheData
|
|
61
|
+
* Cache data.
|
|
62
|
+
*/
|
|
63
|
+
abstract update(nextCheckDateTime: Date, cacheDateTime: Date, cacheData: TCache): Promisable<void>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { AppDataStorage } from "./app-data-storage.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Application data storage using the file system.
|
|
7
|
+
*/
|
|
8
|
+
export class FileAppDataStorage extends AppDataStorage<true> {
|
|
9
|
+
/**
|
|
10
|
+
* Constructor.
|
|
11
|
+
*
|
|
12
|
+
* @param path
|
|
13
|
+
* Storage path prepended to each key along with '/' if defined, empty string if not.
|
|
14
|
+
*/
|
|
15
|
+
constructor(path?: string) {
|
|
16
|
+
super(true, path);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @inheritDoc
|
|
21
|
+
*/
|
|
22
|
+
protected override async doRead(key: string, asBinary: boolean | undefined): Promise<string | Uint8Array | undefined> {
|
|
23
|
+
return fs.promises.readFile(key).then(buffer =>
|
|
24
|
+
asBinary === true ? buffer : buffer.toString()
|
|
25
|
+
).catch(() =>
|
|
26
|
+
undefined
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @inheritDoc
|
|
32
|
+
*/
|
|
33
|
+
protected override async doWrite(key: string, data: string | Uint8Array): Promise<void> {
|
|
34
|
+
return fs.promises.mkdir(path.dirname(key), {
|
|
35
|
+
recursive: true
|
|
36
|
+
}).then(async () =>
|
|
37
|
+
fs.promises.writeFile(key, data)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @inheritDoc
|
|
43
|
+
*/
|
|
44
|
+
protected override async doDelete(key: string): Promise<void> {
|
|
45
|
+
return fs.promises.rm(key, {
|
|
46
|
+
force: true
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/hyperlink.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperlink.
|
|
3
|
+
*/
|
|
4
|
+
export interface Hyperlink {
|
|
5
|
+
/**
|
|
6
|
+
* Reference, relative or absolute depending on the application.
|
|
7
|
+
*/
|
|
8
|
+
readonly reference: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable text.
|
|
12
|
+
*/
|
|
13
|
+
readonly text?: string | undefined;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Additional details.
|
|
17
|
+
*/
|
|
18
|
+
readonly details?: string | undefined;
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,5 +16,15 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export type * from "./type.js";
|
|
18
18
|
export * from "./type-helper.js";
|
|
19
|
+
|
|
19
20
|
export * from "./logger.js";
|
|
21
|
+
|
|
22
|
+
export * from "./app-data.js";
|
|
23
|
+
export * from "./app-data-storage.js";
|
|
24
|
+
export * from "./local-app-data-storage.js";
|
|
25
|
+
export * from "./remote-app-data-storage.js";
|
|
26
|
+
export * from "./cache.js";
|
|
27
|
+
|
|
28
|
+
export type * from "./hyperlink.js";
|
|
29
|
+
|
|
20
30
|
export * from "./locale/i18n.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AppDataStorage } from "./app-data-storage.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local application data storage class for the current environment. This is a variable representing a `Promise` as the
|
|
5
|
+
* implementing class is loaded dynamically to prevent the inclusion of unnecessary node dependencies in a browser
|
|
6
|
+
* environment.
|
|
7
|
+
*/
|
|
8
|
+
export const LocalAppDataStorage: Promise<new (path?: string) => AppDataStorage<boolean>> =
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- localStorage is undefined when running under Node.js.
|
|
10
|
+
globalThis.localStorage === undefined ?
|
|
11
|
+
import("./file-app-data-storage.js").then(module => module.FileAppDataStorage) :
|
|
12
|
+
import("./browser-app-data-storage.js").then(module => module.BrowserAppDataStorage);
|
package/src/locale/i18n.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import i18next, {
|
|
2
|
+
type i18n,
|
|
3
|
+
type LanguageDetectorModule,
|
|
4
|
+
type Module,
|
|
5
|
+
type Newable,
|
|
6
|
+
type NewableModule,
|
|
7
|
+
type Resource
|
|
8
|
+
} from "i18next";
|
|
2
9
|
import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
|
|
3
10
|
import I18nextCLILanguageDetector from "i18next-cli-language-detector";
|
|
11
|
+
import enLocaleResources from "./en/locale-resources.js";
|
|
12
|
+
import frLocaleResources from "./fr/locale-resources.js";
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
15
|
* Locale strings type for generic manipulation.
|
|
@@ -50,14 +59,36 @@ export type I18nEnvironment = typeof I18nEnvironments[I18nEnvironmentKey];
|
|
|
50
59
|
*/
|
|
51
60
|
function toLowerCase(s: string): string {
|
|
52
61
|
// Words with no lower case letters are preserved as they are likely mnemonics.
|
|
53
|
-
return s.split(" ").map(word => /[a-z]
|
|
62
|
+
return s.split(" ").map(word => /[a-z]/u.test(word) ? word.toLowerCase() : word).join(" ");
|
|
54
63
|
}
|
|
55
64
|
|
|
65
|
+
export const coreNS = "aidct_core";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Locale resources type is extracted from the English locale resources object.
|
|
69
|
+
*/
|
|
70
|
+
export type CoreLocaleResources = typeof enLocaleResources;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Core resource bundle.
|
|
74
|
+
*/
|
|
75
|
+
export const coreResourceBundle: Resource = {
|
|
76
|
+
en: {
|
|
77
|
+
aidct_core: enLocaleResources
|
|
78
|
+
},
|
|
79
|
+
fr: {
|
|
80
|
+
aidct_core: frLocaleResources
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Explicit type is necessary because type can't be inferred without additional references.
|
|
85
|
+
export const i18nextCore: i18n = i18next.createInstance();
|
|
86
|
+
|
|
56
87
|
/**
|
|
57
88
|
* Initialize internationalization.
|
|
58
89
|
*
|
|
59
90
|
* @param i18next
|
|
60
|
-
* Internationalization object. As multiple objects
|
|
91
|
+
* Internationalization object. As multiple objects exist, this parameter represents the one for the module for which
|
|
61
92
|
* internationalization is being initialized.
|
|
62
93
|
*
|
|
63
94
|
* @param environment
|
|
@@ -69,59 +100,94 @@ function toLowerCase(s: string): string {
|
|
|
69
100
|
* @param defaultNS
|
|
70
101
|
* Default namespace.
|
|
71
102
|
*
|
|
72
|
-
* @param
|
|
73
|
-
*
|
|
103
|
+
* @param defaultResourceBundle
|
|
104
|
+
* Default resource bundle.
|
|
105
|
+
*
|
|
106
|
+
* @param i18nDependencyInits
|
|
107
|
+
* Dependency internationalization initialization functions.
|
|
74
108
|
*
|
|
75
109
|
* @returns
|
|
76
|
-
*
|
|
110
|
+
* Default resource bundle.
|
|
77
111
|
*/
|
|
78
|
-
export async function
|
|
112
|
+
export async function i18nInit(i18next: i18n, environment: I18nEnvironment, debug: boolean, defaultNS: string, defaultResourceBundle: Resource, ...i18nDependencyInits: Array<(environment: I18nEnvironment, debug: boolean) => Promise<Resource>>): Promise<Resource> {
|
|
79
113
|
// Initialization may be called more than once.
|
|
80
114
|
if (!i18next.isInitialized) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
const mergedResourceBundle: Resource = {};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Merge a package resource bundle into the merged resource bundle.
|
|
119
|
+
*
|
|
120
|
+
* @param resourceBundle
|
|
121
|
+
* Package resource bundle.
|
|
122
|
+
*/
|
|
123
|
+
function mergeResourceBundle(resourceBundle: Resource): void {
|
|
85
124
|
// Merge languages.
|
|
86
|
-
for (const [language,
|
|
87
|
-
if (!(language in
|
|
88
|
-
|
|
125
|
+
for (const [language, languageResourceBundle] of Object.entries(resourceBundle)) {
|
|
126
|
+
if (!(language in mergedResourceBundle)) {
|
|
127
|
+
mergedResourceBundle[language] = {};
|
|
89
128
|
}
|
|
90
129
|
|
|
91
|
-
const
|
|
130
|
+
const mergedLanguageResourceBundle = mergedResourceBundle[language];
|
|
92
131
|
|
|
93
132
|
// Merge namespaces.
|
|
94
|
-
for (const [namespace, resourceKey] of Object.entries(
|
|
95
|
-
|
|
133
|
+
for (const [namespace, resourceKey] of Object.entries(languageResourceBundle)) {
|
|
134
|
+
mergedLanguageResourceBundle[namespace] = resourceKey;
|
|
96
135
|
}
|
|
97
136
|
}
|
|
98
137
|
}
|
|
99
138
|
|
|
100
|
-
|
|
139
|
+
mergeResourceBundle(defaultResourceBundle);
|
|
101
140
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
break;
|
|
141
|
+
// Initialize dependencies and merge their resource bundles.
|
|
142
|
+
await Promise.all(i18nDependencyInits.map(async i18nDependencyInit =>
|
|
143
|
+
i18nDependencyInit(environment, debug).then(mergeResourceBundle))
|
|
144
|
+
).then(() => {
|
|
145
|
+
let module: Module | Newable<Module> | NewableModule<Module>;
|
|
108
146
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
147
|
+
switch (environment) {
|
|
148
|
+
case I18nEnvironments.CLI:
|
|
149
|
+
// TODO Refactor when https://github.com/neet/i18next-cli-language-detector/issues/281 resolved.
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Per above.
|
|
151
|
+
module = I18nextCLILanguageDetector as unknown as LanguageDetectorModule;
|
|
152
|
+
break;
|
|
112
153
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
154
|
+
case I18nEnvironments.Browser:
|
|
155
|
+
module = I18nextBrowserLanguageDetector;
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
throw new Error("Not supported");
|
|
160
|
+
}
|
|
116
161
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
162
|
+
return module;
|
|
163
|
+
}).then(async module =>
|
|
164
|
+
i18next.use(module).init({
|
|
165
|
+
debug,
|
|
166
|
+
resources: mergedResourceBundle,
|
|
167
|
+
fallbackLng: "en",
|
|
168
|
+
defaultNS
|
|
169
|
+
})
|
|
170
|
+
).then(() => {
|
|
123
171
|
// Add toLowerCase function.
|
|
124
172
|
i18next.services.formatter?.add("toLowerCase", value => typeof value === "string" ? toLowerCase(value) : String(value));
|
|
125
173
|
});
|
|
126
174
|
}
|
|
175
|
+
|
|
176
|
+
return defaultResourceBundle;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Initialize internationalization.
|
|
181
|
+
*
|
|
182
|
+
* @param environment
|
|
183
|
+
* Environment in which the application is running.
|
|
184
|
+
*
|
|
185
|
+
* @param debug
|
|
186
|
+
* Debug setting.
|
|
187
|
+
*
|
|
188
|
+
* @returns
|
|
189
|
+
* Core resource bundle.
|
|
190
|
+
*/
|
|
191
|
+
export async function i18nCoreInit(environment: I18nEnvironment, debug = false): Promise<Resource> {
|
|
192
|
+
return i18nInit(i18nextCore, environment, debug, coreNS, coreResourceBundle);
|
|
127
193
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CoreLocaleResources } from "./i18n.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internationalization module.
|
|
5
|
+
*/
|
|
6
|
+
declare module "i18next" {
|
|
7
|
+
/**
|
|
8
|
+
* Custom type options for this package.
|
|
9
|
+
*/
|
|
10
|
+
interface CustomTypeOptions {
|
|
11
|
+
defaultNS: "aidct_core";
|
|
12
|
+
resources: {
|
|
13
|
+
aidct_core: CoreLocaleResources;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|