@form-engine-ts/translator-google-v3 2.9.0 → 2.9.2
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 +6 -0
- package/dist/index.cjs +61 -25
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +61 -25
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,6 +28,12 @@ const translator = createGoogleV3Translator({
|
|
|
28
28
|
const japanese = await translator.translateText("Thank you", "ja", "en");
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Set `glossaryConfig` to a glossary ID or full resource name. An ID is expanded using the configured project and
|
|
32
|
+
location. Use `glossaryResolver` to select a glossary per source/target locale pair; returning `undefined` omits the
|
|
33
|
+
glossary from that request. Glossary responses prefer `glossaryTranslations` and fall back to `translations` per item.
|
|
34
|
+
When this adapter is wrapped with `@form-engine-ts/translator-cache`, the applied glossary resource is included in the
|
|
35
|
+
cache variant automatically.
|
|
36
|
+
|
|
31
37
|
Keep OAuth access tokens on a trusted server. `translateBatch` defaults to at most 250 items and 25,000 UTF-8 bytes per
|
|
32
38
|
request (hard limits: 1,024 items and 30,000 bytes). Network errors, HTTP 429, and HTTP 5xx responses are retried; customize
|
|
33
39
|
batch and retry behavior with `batchLimits` and `retry`.
|
package/dist/index.cjs
CHANGED
|
@@ -84,27 +84,39 @@ function endpoint(value) {
|
|
|
84
84
|
throw new TypeError("apiEndpoint must be a valid absolute URL.", { cause });
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
function parseTranslations(body, expected
|
|
87
|
+
function parseTranslations(body, expected) {
|
|
88
88
|
let parsed;
|
|
89
89
|
try {
|
|
90
90
|
parsed = JSON.parse(body);
|
|
91
91
|
} catch (cause) {
|
|
92
92
|
throw new Error("Google Translation Advanced returned invalid JSON.", { cause });
|
|
93
93
|
}
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (values.length !== expected) {
|
|
99
|
-
throw new Error(`Google Translation Advanced returned ${values.length} translations for ${expected} texts.`);
|
|
94
|
+
const glossaryTranslations = isRecord(parsed) && Array.isArray(parsed.glossaryTranslations) ? parsed.glossaryTranslations : [];
|
|
95
|
+
const translations = isRecord(parsed) && Array.isArray(parsed.translations) ? parsed.translations : [];
|
|
96
|
+
if (glossaryTranslations.length === 0 && translations.length === 0) {
|
|
97
|
+
throw new Error("Google Translation Advanced response is missing translations.");
|
|
100
98
|
}
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
return Array.from({ length: expected }, (_, index) => {
|
|
100
|
+
const glossaryValue = glossaryTranslations[index];
|
|
101
|
+
if (isRecord(glossaryValue) && typeof glossaryValue.translatedText === "string") {
|
|
102
|
+
return glossaryValue.translatedText;
|
|
104
103
|
}
|
|
105
|
-
|
|
104
|
+
const value = translations[index];
|
|
105
|
+
if (isRecord(value) && typeof value.translatedText === "string") return value.translatedText;
|
|
106
|
+
throw new Error(`Google Translation Advanced result at index ${index} is invalid.`);
|
|
106
107
|
});
|
|
107
108
|
}
|
|
109
|
+
function normalizeGlossary(value, projectId, location, name) {
|
|
110
|
+
const config = typeof value === "string" ? { glossary: value } : value;
|
|
111
|
+
const glossary = requireNonEmpty(config.glossary, `${name}.glossary`);
|
|
112
|
+
if (config.ignoreCase !== void 0 && typeof config.ignoreCase !== "boolean") {
|
|
113
|
+
throw new TypeError(`${name}.ignoreCase must be a boolean.`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
glossary: glossary.startsWith("projects/") ? glossary : `projects/${projectId}/locations/${location}/glossaries/${glossary}`,
|
|
117
|
+
...config.ignoreCase === void 0 ? {} : { ignoreCase: config.ignoreCase }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
108
120
|
function validateLabels(labels) {
|
|
109
121
|
if (labels === void 0) return void 0;
|
|
110
122
|
for (const [key, value] of Object.entries(labels)) {
|
|
@@ -123,11 +135,14 @@ function retryAfterMilliseconds(value, now) {
|
|
|
123
135
|
function createGoogleV3Translator(options) {
|
|
124
136
|
const projectId = requireNonEmpty(options?.projectId, "projectId");
|
|
125
137
|
const location = requireNonEmpty(options.location ?? "global", "location");
|
|
126
|
-
if (typeof options.getAccessToken !== "function") throw new TypeError("getAccessToken must be a function.");
|
|
127
138
|
const apiEndpoint = endpoint(options.apiEndpoint);
|
|
128
|
-
const fetchImpl = options.fetchFn ?? globalThis.fetch;
|
|
139
|
+
const fetchImpl = options.fetchFn ?? options.fetchImpl ?? globalThis.fetch;
|
|
129
140
|
if (typeof fetchImpl !== "function")
|
|
130
141
|
throw new Error("Fetch is unavailable. Pass fetchFn when creating the translator.");
|
|
142
|
+
if (typeof options.getAccessToken !== "function" && options.apiKey === void 0) {
|
|
143
|
+
throw new TypeError("getAccessToken or apiKey must be provided.");
|
|
144
|
+
}
|
|
145
|
+
const apiKey = options.apiKey === void 0 ? void 0 : requireNonEmpty(options.apiKey, "apiKey");
|
|
131
146
|
const batchLimits = {
|
|
132
147
|
maxItems: options.batchLimits?.maxItems ?? options.maxBatchSize ?? DEFAULT_MAX_ITEMS,
|
|
133
148
|
maxCharacters: options.batchLimits?.maxCharacters ?? DEFAULT_MAX_CHARACTERS
|
|
@@ -147,13 +162,26 @@ function createGoogleV3Translator(options) {
|
|
|
147
162
|
const random = options.random ?? Math.random;
|
|
148
163
|
const now = options.now ?? Date.now;
|
|
149
164
|
const labels = validateLabels(options.labels);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
if (options.glossaryResolver !== void 0 && typeof options.glossaryResolver !== "function") {
|
|
166
|
+
throw new TypeError("glossaryResolver must be a function.");
|
|
167
|
+
}
|
|
168
|
+
const staticGlossaryConfig = options.glossaryConfig === void 0 ? void 0 : normalizeGlossary(options.glossaryConfig, projectId, location, "glossaryConfig");
|
|
169
|
+
const baseRequestUrl = `${apiEndpoint}/projects/${encodeURIComponent(projectId)}/locations/${encodeURIComponent(location)}:translateText`;
|
|
170
|
+
const requestUrl = apiKey === void 0 ? baseRequestUrl : `${baseRequestUrl}?key=${encodeURIComponent(apiKey)}`;
|
|
171
|
+
const resolveGlossary = (sourceLocale, targetLocale) => {
|
|
172
|
+
if (options.glossaryResolver !== void 0) {
|
|
173
|
+
const resolved = options.glossaryResolver({ sourceLocale: sourceLocale ?? "auto", targetLocale });
|
|
174
|
+
return resolved === void 0 ? void 0 : normalizeGlossary(resolved, projectId, location, "glossaryResolver result");
|
|
175
|
+
}
|
|
176
|
+
return staticGlossaryConfig;
|
|
153
177
|
};
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
const
|
|
178
|
+
const getCacheVariant = (targetLocale, sourceLocale) => {
|
|
179
|
+
const target = requireNonEmpty(targetLocale, "targetLocale");
|
|
180
|
+
const source = sourceLocale === void 0 ? void 0 : requireNonEmpty(sourceLocale, "sourceLocale");
|
|
181
|
+
return resolveGlossary(source, target)?.glossary;
|
|
182
|
+
};
|
|
183
|
+
const translateChunk = async (texts, targetLocale, sourceLocale, glossaryConfig) => {
|
|
184
|
+
const token = options.getAccessToken === void 0 ? void 0 : requireNonEmpty(await options.getAccessToken(), "accessToken");
|
|
157
185
|
const body = JSON.stringify({
|
|
158
186
|
contents: texts,
|
|
159
187
|
mimeType: "text/plain",
|
|
@@ -167,7 +195,10 @@ function createGoogleV3Translator(options) {
|
|
|
167
195
|
try {
|
|
168
196
|
response = await fetchImpl(requestUrl, {
|
|
169
197
|
method: "POST",
|
|
170
|
-
headers: {
|
|
198
|
+
headers: {
|
|
199
|
+
...token === void 0 ? {} : { Authorization: `Bearer ${token}` },
|
|
200
|
+
"Content-Type": "application/json"
|
|
201
|
+
},
|
|
171
202
|
body
|
|
172
203
|
});
|
|
173
204
|
} catch (cause) {
|
|
@@ -177,7 +208,7 @@ function createGoogleV3Translator(options) {
|
|
|
177
208
|
}
|
|
178
209
|
if (response?.ok === true) {
|
|
179
210
|
return {
|
|
180
|
-
translations: parseTranslations(await response.text(), texts.length
|
|
211
|
+
translations: parseTranslations(await response.text(), texts.length),
|
|
181
212
|
retryAttempts: attempt
|
|
182
213
|
};
|
|
183
214
|
}
|
|
@@ -191,7 +222,8 @@ function createGoogleV3Translator(options) {
|
|
|
191
222
|
continue;
|
|
192
223
|
}
|
|
193
224
|
if (response === void 0) throw new Error("Google Translation Advanced request failed.");
|
|
194
|
-
const
|
|
225
|
+
const responseText = await response.text();
|
|
226
|
+
const detail = token === void 0 ? responseText : responseText.replaceAll(token, "[redacted]");
|
|
195
227
|
throw new Error(
|
|
196
228
|
`Google Translation Advanced request failed with HTTP ${response.status}${detail.length === 0 ? "." : `: ${detail}`}`
|
|
197
229
|
);
|
|
@@ -203,6 +235,7 @@ function createGoogleV3Translator(options) {
|
|
|
203
235
|
}
|
|
204
236
|
const target = requireNonEmpty(targetLocale, "targetLocale");
|
|
205
237
|
const source = sourceLocale === void 0 ? void 0 : requireNonEmpty(sourceLocale, "sourceLocale");
|
|
238
|
+
const glossary = resolveGlossary(source, target);
|
|
206
239
|
const nonEmptyEntries = texts.flatMap((text, index) => text.trim().length === 0 ? [] : [{ text, index }]);
|
|
207
240
|
const nonEmptyTexts = nonEmptyEntries.map((entry) => entry.text);
|
|
208
241
|
const chunks = splitTranslationBatch(nonEmptyTexts, batchLimits);
|
|
@@ -210,7 +243,7 @@ function createGoogleV3Translator(options) {
|
|
|
210
243
|
const translated = [];
|
|
211
244
|
let retryAttempts = 0;
|
|
212
245
|
for (const chunk of chunks) {
|
|
213
|
-
const result = await translateChunk(chunk, target, source);
|
|
246
|
+
const result = await translateChunk(chunk, target, source, glossary);
|
|
214
247
|
translated.push(...result.translations);
|
|
215
248
|
retryAttempts += result.retryAttempts;
|
|
216
249
|
}
|
|
@@ -227,11 +260,13 @@ function createGoogleV3Translator(options) {
|
|
|
227
260
|
durationMs: Math.max(0, now() - startedAt),
|
|
228
261
|
cacheHitCount: 0,
|
|
229
262
|
cacheMissCount: nonEmptyTexts.length,
|
|
230
|
-
evictionCount: 0
|
|
263
|
+
evictionCount: 0,
|
|
264
|
+
...glossary === void 0 ? {} : { glossary: glossary.glossary }
|
|
231
265
|
});
|
|
232
266
|
return restored;
|
|
233
267
|
};
|
|
234
|
-
|
|
268
|
+
const translator = {
|
|
269
|
+
getCacheVariant,
|
|
235
270
|
async translateText(text, targetLocale, sourceLocale) {
|
|
236
271
|
if (typeof text !== "string") throw new TypeError("text must be a string.");
|
|
237
272
|
const translated = await translateBatch([text], targetLocale, sourceLocale);
|
|
@@ -241,6 +276,7 @@ function createGoogleV3Translator(options) {
|
|
|
241
276
|
},
|
|
242
277
|
translateBatch
|
|
243
278
|
};
|
|
279
|
+
return translator;
|
|
244
280
|
}
|
|
245
281
|
// Annotate the CommonJS export names for ESM import in node:
|
|
246
282
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { AsyncTranslationAdapter } from '@form-engine-ts/core';
|
|
2
2
|
|
|
3
3
|
interface GoogleV3GlossaryConfig {
|
|
4
|
+
/** Glossary ID or fully qualified glossary resource name. */
|
|
4
5
|
readonly glossary: string;
|
|
6
|
+
/** Whether glossary matching should ignore case. */
|
|
5
7
|
readonly ignoreCase?: boolean;
|
|
6
8
|
}
|
|
9
|
+
type GlossaryResolver = (context: {
|
|
10
|
+
readonly sourceLocale: string;
|
|
11
|
+
readonly targetLocale: string;
|
|
12
|
+
}) => string | GoogleV3GlossaryConfig | undefined;
|
|
13
|
+
interface GoogleV3TranslationAdapter extends AsyncTranslationAdapter {
|
|
14
|
+
/** Returns the glossary resource used for a locale pair, for cache-key isolation. */
|
|
15
|
+
getCacheVariant(targetLocale: string, sourceLocale?: string): string | undefined;
|
|
16
|
+
}
|
|
7
17
|
interface GoogleV3TranslatorOptions {
|
|
8
18
|
readonly projectId: string;
|
|
9
19
|
readonly location?: string;
|
|
10
|
-
readonly getAccessToken
|
|
11
|
-
readonly
|
|
20
|
+
readonly getAccessToken?: () => Promise<string> | string;
|
|
21
|
+
readonly apiKey?: string;
|
|
22
|
+
readonly glossaryConfig?: GoogleV3GlossaryConfig | string;
|
|
23
|
+
readonly glossaryResolver?: GlossaryResolver;
|
|
12
24
|
readonly labels?: Readonly<Record<string, string>>;
|
|
13
25
|
readonly fetchFn?: typeof fetch;
|
|
26
|
+
readonly fetchImpl?: typeof fetch;
|
|
14
27
|
readonly apiEndpoint?: string;
|
|
15
28
|
readonly batchLimits?: BatchSplitLimits;
|
|
16
29
|
readonly retry?: RetryConfig;
|
|
@@ -41,8 +54,9 @@ interface TranslationBatchReport {
|
|
|
41
54
|
readonly cacheHitCount: number;
|
|
42
55
|
readonly cacheMissCount: number;
|
|
43
56
|
readonly evictionCount: number;
|
|
57
|
+
readonly glossary?: string;
|
|
44
58
|
}
|
|
45
59
|
declare function splitTranslationBatch(texts: readonly string[], limits?: BatchSplitLimits): string[][];
|
|
46
60
|
declare function createGoogleV3Translator(options: GoogleV3TranslatorOptions): AsyncTranslationAdapter;
|
|
47
61
|
|
|
48
|
-
export { type BatchSplitLimits, type GoogleV3GlossaryConfig, type GoogleV3TranslatorOptions, type RetryConfig, type TranslationBatchReport, createGoogleV3Translator, splitTranslationBatch };
|
|
62
|
+
export { type BatchSplitLimits, type GlossaryResolver, type GoogleV3GlossaryConfig, type GoogleV3TranslationAdapter, type GoogleV3TranslatorOptions, type RetryConfig, type TranslationBatchReport, createGoogleV3Translator, splitTranslationBatch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { AsyncTranslationAdapter } from '@form-engine-ts/core';
|
|
2
2
|
|
|
3
3
|
interface GoogleV3GlossaryConfig {
|
|
4
|
+
/** Glossary ID or fully qualified glossary resource name. */
|
|
4
5
|
readonly glossary: string;
|
|
6
|
+
/** Whether glossary matching should ignore case. */
|
|
5
7
|
readonly ignoreCase?: boolean;
|
|
6
8
|
}
|
|
9
|
+
type GlossaryResolver = (context: {
|
|
10
|
+
readonly sourceLocale: string;
|
|
11
|
+
readonly targetLocale: string;
|
|
12
|
+
}) => string | GoogleV3GlossaryConfig | undefined;
|
|
13
|
+
interface GoogleV3TranslationAdapter extends AsyncTranslationAdapter {
|
|
14
|
+
/** Returns the glossary resource used for a locale pair, for cache-key isolation. */
|
|
15
|
+
getCacheVariant(targetLocale: string, sourceLocale?: string): string | undefined;
|
|
16
|
+
}
|
|
7
17
|
interface GoogleV3TranslatorOptions {
|
|
8
18
|
readonly projectId: string;
|
|
9
19
|
readonly location?: string;
|
|
10
|
-
readonly getAccessToken
|
|
11
|
-
readonly
|
|
20
|
+
readonly getAccessToken?: () => Promise<string> | string;
|
|
21
|
+
readonly apiKey?: string;
|
|
22
|
+
readonly glossaryConfig?: GoogleV3GlossaryConfig | string;
|
|
23
|
+
readonly glossaryResolver?: GlossaryResolver;
|
|
12
24
|
readonly labels?: Readonly<Record<string, string>>;
|
|
13
25
|
readonly fetchFn?: typeof fetch;
|
|
26
|
+
readonly fetchImpl?: typeof fetch;
|
|
14
27
|
readonly apiEndpoint?: string;
|
|
15
28
|
readonly batchLimits?: BatchSplitLimits;
|
|
16
29
|
readonly retry?: RetryConfig;
|
|
@@ -41,8 +54,9 @@ interface TranslationBatchReport {
|
|
|
41
54
|
readonly cacheHitCount: number;
|
|
42
55
|
readonly cacheMissCount: number;
|
|
43
56
|
readonly evictionCount: number;
|
|
57
|
+
readonly glossary?: string;
|
|
44
58
|
}
|
|
45
59
|
declare function splitTranslationBatch(texts: readonly string[], limits?: BatchSplitLimits): string[][];
|
|
46
60
|
declare function createGoogleV3Translator(options: GoogleV3TranslatorOptions): AsyncTranslationAdapter;
|
|
47
61
|
|
|
48
|
-
export { type BatchSplitLimits, type GoogleV3GlossaryConfig, type GoogleV3TranslatorOptions, type RetryConfig, type TranslationBatchReport, createGoogleV3Translator, splitTranslationBatch };
|
|
62
|
+
export { type BatchSplitLimits, type GlossaryResolver, type GoogleV3GlossaryConfig, type GoogleV3TranslationAdapter, type GoogleV3TranslatorOptions, type RetryConfig, type TranslationBatchReport, createGoogleV3Translator, splitTranslationBatch };
|
package/dist/index.js
CHANGED
|
@@ -59,27 +59,39 @@ function endpoint(value) {
|
|
|
59
59
|
throw new TypeError("apiEndpoint must be a valid absolute URL.", { cause });
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
function parseTranslations(body, expected
|
|
62
|
+
function parseTranslations(body, expected) {
|
|
63
63
|
let parsed;
|
|
64
64
|
try {
|
|
65
65
|
parsed = JSON.parse(body);
|
|
66
66
|
} catch (cause) {
|
|
67
67
|
throw new Error("Google Translation Advanced returned invalid JSON.", { cause });
|
|
68
68
|
}
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (values.length !== expected) {
|
|
74
|
-
throw new Error(`Google Translation Advanced returned ${values.length} translations for ${expected} texts.`);
|
|
69
|
+
const glossaryTranslations = isRecord(parsed) && Array.isArray(parsed.glossaryTranslations) ? parsed.glossaryTranslations : [];
|
|
70
|
+
const translations = isRecord(parsed) && Array.isArray(parsed.translations) ? parsed.translations : [];
|
|
71
|
+
if (glossaryTranslations.length === 0 && translations.length === 0) {
|
|
72
|
+
throw new Error("Google Translation Advanced response is missing translations.");
|
|
75
73
|
}
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
return Array.from({ length: expected }, (_, index) => {
|
|
75
|
+
const glossaryValue = glossaryTranslations[index];
|
|
76
|
+
if (isRecord(glossaryValue) && typeof glossaryValue.translatedText === "string") {
|
|
77
|
+
return glossaryValue.translatedText;
|
|
79
78
|
}
|
|
80
|
-
|
|
79
|
+
const value = translations[index];
|
|
80
|
+
if (isRecord(value) && typeof value.translatedText === "string") return value.translatedText;
|
|
81
|
+
throw new Error(`Google Translation Advanced result at index ${index} is invalid.`);
|
|
81
82
|
});
|
|
82
83
|
}
|
|
84
|
+
function normalizeGlossary(value, projectId, location, name) {
|
|
85
|
+
const config = typeof value === "string" ? { glossary: value } : value;
|
|
86
|
+
const glossary = requireNonEmpty(config.glossary, `${name}.glossary`);
|
|
87
|
+
if (config.ignoreCase !== void 0 && typeof config.ignoreCase !== "boolean") {
|
|
88
|
+
throw new TypeError(`${name}.ignoreCase must be a boolean.`);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
glossary: glossary.startsWith("projects/") ? glossary : `projects/${projectId}/locations/${location}/glossaries/${glossary}`,
|
|
92
|
+
...config.ignoreCase === void 0 ? {} : { ignoreCase: config.ignoreCase }
|
|
93
|
+
};
|
|
94
|
+
}
|
|
83
95
|
function validateLabels(labels) {
|
|
84
96
|
if (labels === void 0) return void 0;
|
|
85
97
|
for (const [key, value] of Object.entries(labels)) {
|
|
@@ -98,11 +110,14 @@ function retryAfterMilliseconds(value, now) {
|
|
|
98
110
|
function createGoogleV3Translator(options) {
|
|
99
111
|
const projectId = requireNonEmpty(options?.projectId, "projectId");
|
|
100
112
|
const location = requireNonEmpty(options.location ?? "global", "location");
|
|
101
|
-
if (typeof options.getAccessToken !== "function") throw new TypeError("getAccessToken must be a function.");
|
|
102
113
|
const apiEndpoint = endpoint(options.apiEndpoint);
|
|
103
|
-
const fetchImpl = options.fetchFn ?? globalThis.fetch;
|
|
114
|
+
const fetchImpl = options.fetchFn ?? options.fetchImpl ?? globalThis.fetch;
|
|
104
115
|
if (typeof fetchImpl !== "function")
|
|
105
116
|
throw new Error("Fetch is unavailable. Pass fetchFn when creating the translator.");
|
|
117
|
+
if (typeof options.getAccessToken !== "function" && options.apiKey === void 0) {
|
|
118
|
+
throw new TypeError("getAccessToken or apiKey must be provided.");
|
|
119
|
+
}
|
|
120
|
+
const apiKey = options.apiKey === void 0 ? void 0 : requireNonEmpty(options.apiKey, "apiKey");
|
|
106
121
|
const batchLimits = {
|
|
107
122
|
maxItems: options.batchLimits?.maxItems ?? options.maxBatchSize ?? DEFAULT_MAX_ITEMS,
|
|
108
123
|
maxCharacters: options.batchLimits?.maxCharacters ?? DEFAULT_MAX_CHARACTERS
|
|
@@ -122,13 +137,26 @@ function createGoogleV3Translator(options) {
|
|
|
122
137
|
const random = options.random ?? Math.random;
|
|
123
138
|
const now = options.now ?? Date.now;
|
|
124
139
|
const labels = validateLabels(options.labels);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
if (options.glossaryResolver !== void 0 && typeof options.glossaryResolver !== "function") {
|
|
141
|
+
throw new TypeError("glossaryResolver must be a function.");
|
|
142
|
+
}
|
|
143
|
+
const staticGlossaryConfig = options.glossaryConfig === void 0 ? void 0 : normalizeGlossary(options.glossaryConfig, projectId, location, "glossaryConfig");
|
|
144
|
+
const baseRequestUrl = `${apiEndpoint}/projects/${encodeURIComponent(projectId)}/locations/${encodeURIComponent(location)}:translateText`;
|
|
145
|
+
const requestUrl = apiKey === void 0 ? baseRequestUrl : `${baseRequestUrl}?key=${encodeURIComponent(apiKey)}`;
|
|
146
|
+
const resolveGlossary = (sourceLocale, targetLocale) => {
|
|
147
|
+
if (options.glossaryResolver !== void 0) {
|
|
148
|
+
const resolved = options.glossaryResolver({ sourceLocale: sourceLocale ?? "auto", targetLocale });
|
|
149
|
+
return resolved === void 0 ? void 0 : normalizeGlossary(resolved, projectId, location, "glossaryResolver result");
|
|
150
|
+
}
|
|
151
|
+
return staticGlossaryConfig;
|
|
128
152
|
};
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const
|
|
153
|
+
const getCacheVariant = (targetLocale, sourceLocale) => {
|
|
154
|
+
const target = requireNonEmpty(targetLocale, "targetLocale");
|
|
155
|
+
const source = sourceLocale === void 0 ? void 0 : requireNonEmpty(sourceLocale, "sourceLocale");
|
|
156
|
+
return resolveGlossary(source, target)?.glossary;
|
|
157
|
+
};
|
|
158
|
+
const translateChunk = async (texts, targetLocale, sourceLocale, glossaryConfig) => {
|
|
159
|
+
const token = options.getAccessToken === void 0 ? void 0 : requireNonEmpty(await options.getAccessToken(), "accessToken");
|
|
132
160
|
const body = JSON.stringify({
|
|
133
161
|
contents: texts,
|
|
134
162
|
mimeType: "text/plain",
|
|
@@ -142,7 +170,10 @@ function createGoogleV3Translator(options) {
|
|
|
142
170
|
try {
|
|
143
171
|
response = await fetchImpl(requestUrl, {
|
|
144
172
|
method: "POST",
|
|
145
|
-
headers: {
|
|
173
|
+
headers: {
|
|
174
|
+
...token === void 0 ? {} : { Authorization: `Bearer ${token}` },
|
|
175
|
+
"Content-Type": "application/json"
|
|
176
|
+
},
|
|
146
177
|
body
|
|
147
178
|
});
|
|
148
179
|
} catch (cause) {
|
|
@@ -152,7 +183,7 @@ function createGoogleV3Translator(options) {
|
|
|
152
183
|
}
|
|
153
184
|
if (response?.ok === true) {
|
|
154
185
|
return {
|
|
155
|
-
translations: parseTranslations(await response.text(), texts.length
|
|
186
|
+
translations: parseTranslations(await response.text(), texts.length),
|
|
156
187
|
retryAttempts: attempt
|
|
157
188
|
};
|
|
158
189
|
}
|
|
@@ -166,7 +197,8 @@ function createGoogleV3Translator(options) {
|
|
|
166
197
|
continue;
|
|
167
198
|
}
|
|
168
199
|
if (response === void 0) throw new Error("Google Translation Advanced request failed.");
|
|
169
|
-
const
|
|
200
|
+
const responseText = await response.text();
|
|
201
|
+
const detail = token === void 0 ? responseText : responseText.replaceAll(token, "[redacted]");
|
|
170
202
|
throw new Error(
|
|
171
203
|
`Google Translation Advanced request failed with HTTP ${response.status}${detail.length === 0 ? "." : `: ${detail}`}`
|
|
172
204
|
);
|
|
@@ -178,6 +210,7 @@ function createGoogleV3Translator(options) {
|
|
|
178
210
|
}
|
|
179
211
|
const target = requireNonEmpty(targetLocale, "targetLocale");
|
|
180
212
|
const source = sourceLocale === void 0 ? void 0 : requireNonEmpty(sourceLocale, "sourceLocale");
|
|
213
|
+
const glossary = resolveGlossary(source, target);
|
|
181
214
|
const nonEmptyEntries = texts.flatMap((text, index) => text.trim().length === 0 ? [] : [{ text, index }]);
|
|
182
215
|
const nonEmptyTexts = nonEmptyEntries.map((entry) => entry.text);
|
|
183
216
|
const chunks = splitTranslationBatch(nonEmptyTexts, batchLimits);
|
|
@@ -185,7 +218,7 @@ function createGoogleV3Translator(options) {
|
|
|
185
218
|
const translated = [];
|
|
186
219
|
let retryAttempts = 0;
|
|
187
220
|
for (const chunk of chunks) {
|
|
188
|
-
const result = await translateChunk(chunk, target, source);
|
|
221
|
+
const result = await translateChunk(chunk, target, source, glossary);
|
|
189
222
|
translated.push(...result.translations);
|
|
190
223
|
retryAttempts += result.retryAttempts;
|
|
191
224
|
}
|
|
@@ -202,11 +235,13 @@ function createGoogleV3Translator(options) {
|
|
|
202
235
|
durationMs: Math.max(0, now() - startedAt),
|
|
203
236
|
cacheHitCount: 0,
|
|
204
237
|
cacheMissCount: nonEmptyTexts.length,
|
|
205
|
-
evictionCount: 0
|
|
238
|
+
evictionCount: 0,
|
|
239
|
+
...glossary === void 0 ? {} : { glossary: glossary.glossary }
|
|
206
240
|
});
|
|
207
241
|
return restored;
|
|
208
242
|
};
|
|
209
|
-
|
|
243
|
+
const translator = {
|
|
244
|
+
getCacheVariant,
|
|
210
245
|
async translateText(text, targetLocale, sourceLocale) {
|
|
211
246
|
if (typeof text !== "string") throw new TypeError("text must be a string.");
|
|
212
247
|
const translated = await translateBatch([text], targetLocale, sourceLocale);
|
|
@@ -216,6 +251,7 @@ function createGoogleV3Translator(options) {
|
|
|
216
251
|
},
|
|
217
252
|
translateBatch
|
|
218
253
|
};
|
|
254
|
+
return translator;
|
|
219
255
|
}
|
|
220
256
|
export {
|
|
221
257
|
createGoogleV3Translator,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/translator-google-v3",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"typescript"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@form-engine-ts/core": "2.9.
|
|
43
|
+
"@form-engine-ts/core": "2.9.2"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|