@form-engine-ts/translator-cache 2.7.0 → 2.8.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/README.md +2 -0
- package/dist/index.cjs +34 -2
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +34 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,3 +25,5 @@ const translator = withTranslationCache(baseTranslator, cache, {
|
|
|
25
25
|
Keys isolate adapter name, source locale, target locale, and a deterministic UTF-8 hash of the source text. Batch misses
|
|
26
26
|
are deduplicated, translated once in source order, cached, and restored to their original positions. The built-in memory
|
|
27
27
|
cache applies both TTL expiration and bounded LRU eviction and exposes `size`, `evictionCount`, and `clear()` diagnostics.
|
|
28
|
+
Use `variant` to isolate glossary/model/configuration revisions, or `buildKey` for complete key control.
|
|
29
|
+
`onStatsReport` receives cumulative real cache hits, misses, evictions, and current size after successful translations.
|
package/dist/index.cjs
CHANGED
|
@@ -107,14 +107,44 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
107
107
|
if (prefix.trim().length === 0) throw new TypeError("keyPrefix must not be empty.");
|
|
108
108
|
const adapterName = options.adapterName ?? "anonymous";
|
|
109
109
|
if (adapterName.trim().length === 0) throw new TypeError("adapterName must not be empty.");
|
|
110
|
+
if (options.variant !== void 0 && options.variant.trim().length === 0) {
|
|
111
|
+
throw new TypeError("variant must not be empty.");
|
|
112
|
+
}
|
|
113
|
+
if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
|
|
114
|
+
throw new TypeError("buildKey must be a function.");
|
|
115
|
+
}
|
|
116
|
+
const initialEvictionCount = cache.evictionCount ?? 0;
|
|
117
|
+
let hits = 0;
|
|
118
|
+
let misses = 0;
|
|
119
|
+
const reportStats = () => {
|
|
120
|
+
const size = cache.size ?? 0;
|
|
121
|
+
options.onStatsReport?.({
|
|
122
|
+
hits,
|
|
123
|
+
misses,
|
|
124
|
+
evictions: Math.max(0, (cache.evictionCount ?? initialEvictionCount) - initialEvictionCount),
|
|
125
|
+
size
|
|
126
|
+
});
|
|
127
|
+
};
|
|
110
128
|
const translateBatch = async (texts, targetLocale, sourceLocale) => {
|
|
111
129
|
if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
|
|
112
130
|
throw new TypeError("texts must be an array of strings.");
|
|
113
131
|
}
|
|
114
132
|
const target = requireLocale(targetLocale, "targetLocale");
|
|
115
133
|
const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
|
|
116
|
-
const keys = texts.map((text) =>
|
|
134
|
+
const keys = texts.map((text) => {
|
|
135
|
+
const context = {
|
|
136
|
+
sourceText: text,
|
|
137
|
+
sourceLocale: source,
|
|
138
|
+
targetLocale: target,
|
|
139
|
+
...options.variant === void 0 ? {} : { variant: options.variant }
|
|
140
|
+
};
|
|
141
|
+
const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${options.variant === void 0 ? "" : `${options.variant}:`}${source}:${target}:${hashTranslationText(text)}`;
|
|
142
|
+
if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
|
|
143
|
+
return key;
|
|
144
|
+
});
|
|
117
145
|
const cached = await Promise.all(keys.map((key) => cache.get(key)));
|
|
146
|
+
hits += cached.filter((value) => value !== void 0).length;
|
|
147
|
+
misses += cached.filter((value) => value === void 0).length;
|
|
118
148
|
const missingByKey = /* @__PURE__ */ new Map();
|
|
119
149
|
for (let index = 0; index < texts.length; index += 1) {
|
|
120
150
|
if (cached[index] !== void 0) continue;
|
|
@@ -144,10 +174,12 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
144
174
|
})
|
|
145
175
|
);
|
|
146
176
|
}
|
|
147
|
-
|
|
177
|
+
const result = cached.map((value) => {
|
|
148
178
|
if (value === void 0) throw new Error("Translation cache result is unavailable.");
|
|
149
179
|
return value;
|
|
150
180
|
});
|
|
181
|
+
reportStats();
|
|
182
|
+
return result;
|
|
151
183
|
};
|
|
152
184
|
return {
|
|
153
185
|
async translateText(text, targetLocale, sourceLocale) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { AsyncTranslationAdapter } from '@form-engine-ts/core';
|
|
2
2
|
|
|
3
3
|
interface TranslationCacheStorage {
|
|
4
|
+
readonly size?: number;
|
|
5
|
+
readonly evictionCount?: number;
|
|
4
6
|
get(key: string): Promise<string | undefined> | string | undefined;
|
|
5
7
|
set(key: string, value: string, ttlMs?: number): Promise<void> | void;
|
|
6
8
|
}
|
|
9
|
+
interface TranslationCacheKeyContext {
|
|
10
|
+
readonly sourceText: string;
|
|
11
|
+
readonly sourceLocale: string;
|
|
12
|
+
readonly targetLocale: string;
|
|
13
|
+
readonly variant?: string;
|
|
14
|
+
}
|
|
15
|
+
interface TranslationCacheStats {
|
|
16
|
+
readonly hits: number;
|
|
17
|
+
readonly misses: number;
|
|
18
|
+
readonly evictions: number;
|
|
19
|
+
readonly size: number;
|
|
20
|
+
}
|
|
7
21
|
interface TranslationCacheOptions {
|
|
8
22
|
readonly ttlMs?: number;
|
|
9
23
|
readonly keyPrefix?: string;
|
|
10
24
|
readonly adapterName?: string;
|
|
25
|
+
readonly variant?: string;
|
|
26
|
+
readonly buildKey?: (context: TranslationCacheKeyContext) => string;
|
|
27
|
+
readonly onStatsReport?: (stats: TranslationCacheStats) => void;
|
|
11
28
|
}
|
|
12
29
|
interface MemoryTranslationCacheOptions {
|
|
13
30
|
readonly maxEntries?: number;
|
|
@@ -23,4 +40,4 @@ declare function hashTranslationText(text: string): string;
|
|
|
23
40
|
declare function createMemoryTranslationCache(options?: MemoryTranslationCacheOptions): MemoryTranslationCache;
|
|
24
41
|
declare function withTranslationCache(baseAdapter: AsyncTranslationAdapter, cache: TranslationCacheStorage, options?: TranslationCacheOptions): AsyncTranslationAdapter;
|
|
25
42
|
|
|
26
|
-
export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheOptions, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
|
|
43
|
+
export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheKeyContext, type TranslationCacheOptions, type TranslationCacheStats, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { AsyncTranslationAdapter } from '@form-engine-ts/core';
|
|
2
2
|
|
|
3
3
|
interface TranslationCacheStorage {
|
|
4
|
+
readonly size?: number;
|
|
5
|
+
readonly evictionCount?: number;
|
|
4
6
|
get(key: string): Promise<string | undefined> | string | undefined;
|
|
5
7
|
set(key: string, value: string, ttlMs?: number): Promise<void> | void;
|
|
6
8
|
}
|
|
9
|
+
interface TranslationCacheKeyContext {
|
|
10
|
+
readonly sourceText: string;
|
|
11
|
+
readonly sourceLocale: string;
|
|
12
|
+
readonly targetLocale: string;
|
|
13
|
+
readonly variant?: string;
|
|
14
|
+
}
|
|
15
|
+
interface TranslationCacheStats {
|
|
16
|
+
readonly hits: number;
|
|
17
|
+
readonly misses: number;
|
|
18
|
+
readonly evictions: number;
|
|
19
|
+
readonly size: number;
|
|
20
|
+
}
|
|
7
21
|
interface TranslationCacheOptions {
|
|
8
22
|
readonly ttlMs?: number;
|
|
9
23
|
readonly keyPrefix?: string;
|
|
10
24
|
readonly adapterName?: string;
|
|
25
|
+
readonly variant?: string;
|
|
26
|
+
readonly buildKey?: (context: TranslationCacheKeyContext) => string;
|
|
27
|
+
readonly onStatsReport?: (stats: TranslationCacheStats) => void;
|
|
11
28
|
}
|
|
12
29
|
interface MemoryTranslationCacheOptions {
|
|
13
30
|
readonly maxEntries?: number;
|
|
@@ -23,4 +40,4 @@ declare function hashTranslationText(text: string): string;
|
|
|
23
40
|
declare function createMemoryTranslationCache(options?: MemoryTranslationCacheOptions): MemoryTranslationCache;
|
|
24
41
|
declare function withTranslationCache(baseAdapter: AsyncTranslationAdapter, cache: TranslationCacheStorage, options?: TranslationCacheOptions): AsyncTranslationAdapter;
|
|
25
42
|
|
|
26
|
-
export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheOptions, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
|
|
43
|
+
export { type MemoryTranslationCache, type MemoryTranslationCacheOptions, type TranslationCacheKeyContext, type TranslationCacheOptions, type TranslationCacheStats, type TranslationCacheStorage, createMemoryTranslationCache, hashTranslationText, withTranslationCache };
|
package/dist/index.js
CHANGED
|
@@ -81,14 +81,44 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
81
81
|
if (prefix.trim().length === 0) throw new TypeError("keyPrefix must not be empty.");
|
|
82
82
|
const adapterName = options.adapterName ?? "anonymous";
|
|
83
83
|
if (adapterName.trim().length === 0) throw new TypeError("adapterName must not be empty.");
|
|
84
|
+
if (options.variant !== void 0 && options.variant.trim().length === 0) {
|
|
85
|
+
throw new TypeError("variant must not be empty.");
|
|
86
|
+
}
|
|
87
|
+
if (options.buildKey !== void 0 && typeof options.buildKey !== "function") {
|
|
88
|
+
throw new TypeError("buildKey must be a function.");
|
|
89
|
+
}
|
|
90
|
+
const initialEvictionCount = cache.evictionCount ?? 0;
|
|
91
|
+
let hits = 0;
|
|
92
|
+
let misses = 0;
|
|
93
|
+
const reportStats = () => {
|
|
94
|
+
const size = cache.size ?? 0;
|
|
95
|
+
options.onStatsReport?.({
|
|
96
|
+
hits,
|
|
97
|
+
misses,
|
|
98
|
+
evictions: Math.max(0, (cache.evictionCount ?? initialEvictionCount) - initialEvictionCount),
|
|
99
|
+
size
|
|
100
|
+
});
|
|
101
|
+
};
|
|
84
102
|
const translateBatch = async (texts, targetLocale, sourceLocale) => {
|
|
85
103
|
if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
|
|
86
104
|
throw new TypeError("texts must be an array of strings.");
|
|
87
105
|
}
|
|
88
106
|
const target = requireLocale(targetLocale, "targetLocale");
|
|
89
107
|
const source = sourceLocale === void 0 ? "auto" : requireLocale(sourceLocale, "sourceLocale");
|
|
90
|
-
const keys = texts.map((text) =>
|
|
108
|
+
const keys = texts.map((text) => {
|
|
109
|
+
const context = {
|
|
110
|
+
sourceText: text,
|
|
111
|
+
sourceLocale: source,
|
|
112
|
+
targetLocale: target,
|
|
113
|
+
...options.variant === void 0 ? {} : { variant: options.variant }
|
|
114
|
+
};
|
|
115
|
+
const key = options.buildKey?.(context) ?? `${prefix}:${adapterName}:${options.variant === void 0 ? "" : `${options.variant}:`}${source}:${target}:${hashTranslationText(text)}`;
|
|
116
|
+
if (typeof key !== "string" || key.length === 0) throw new TypeError("Translation cache key must not be empty.");
|
|
117
|
+
return key;
|
|
118
|
+
});
|
|
91
119
|
const cached = await Promise.all(keys.map((key) => cache.get(key)));
|
|
120
|
+
hits += cached.filter((value) => value !== void 0).length;
|
|
121
|
+
misses += cached.filter((value) => value === void 0).length;
|
|
92
122
|
const missingByKey = /* @__PURE__ */ new Map();
|
|
93
123
|
for (let index = 0; index < texts.length; index += 1) {
|
|
94
124
|
if (cached[index] !== void 0) continue;
|
|
@@ -118,10 +148,12 @@ function withTranslationCache(baseAdapter, cache, options = {}) {
|
|
|
118
148
|
})
|
|
119
149
|
);
|
|
120
150
|
}
|
|
121
|
-
|
|
151
|
+
const result = cached.map((value) => {
|
|
122
152
|
if (value === void 0) throw new Error("Translation cache result is unavailable.");
|
|
123
153
|
return value;
|
|
124
154
|
});
|
|
155
|
+
reportStats();
|
|
156
|
+
return result;
|
|
125
157
|
};
|
|
126
158
|
return {
|
|
127
159
|
async translateText(text, targetLocale, sourceLocale) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/translator-cache",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"typescript"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@form-engine-ts/core": "2.
|
|
41
|
+
"@form-engine-ts/core": "2.8.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|