@form-engine-ts/translator-google 1.0.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/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/index.cjs +182 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +157 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nitta-a
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @form-engine-ts/translator-google
|
|
2
|
+
|
|
3
|
+
Server-side Google Cloud Translation Basic v2 adapter for form-engine-ts with API Key or Bearer authentication.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @form-engine-ts/core @form-engine-ts/translator-google
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createGoogleTranslator } from "@form-engine-ts/translator-google";
|
|
15
|
+
|
|
16
|
+
const translator = createGoogleTranslator({
|
|
17
|
+
apiKey: process.env.GOOGLE_TRANSLATE_API_KEY!
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const japanese = await translator.translateText("Thank you", "ja", "en");
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Keep credentials on a trusted server. For OAuth2 or Service Account authentication, provide `getAccessToken` instead of `apiKey`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createGoogleTranslator: () => createGoogleTranslator
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var DEFAULT_ENDPOINT = "https://translation.googleapis.com/language/translate/v2";
|
|
27
|
+
var MAX_BATCH_SIZE = 128;
|
|
28
|
+
var NAMED_ENTITIES = {
|
|
29
|
+
quot: '"',
|
|
30
|
+
apos: "'",
|
|
31
|
+
amp: "&",
|
|
32
|
+
lt: "<",
|
|
33
|
+
gt: ">"
|
|
34
|
+
};
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function requireNonEmpty(value, name) {
|
|
39
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
40
|
+
throw new TypeError(`${name} must be a non-empty string.`);
|
|
41
|
+
}
|
|
42
|
+
return value.trim();
|
|
43
|
+
}
|
|
44
|
+
function decodeHtmlEntities(text) {
|
|
45
|
+
return text.replace(/&(?:quot|apos|amp|lt|gt|#(?:[xX][0-9a-fA-F]+|[0-9]+));/g, (entity) => {
|
|
46
|
+
if (!entity.startsWith("&#")) return NAMED_ENTITIES[entity.slice(1, -1)] ?? entity;
|
|
47
|
+
const encoded = entity.slice(2, -1);
|
|
48
|
+
const hexadecimal = encoded.startsWith("x") || encoded.startsWith("X");
|
|
49
|
+
const codePoint = Number.parseInt(hexadecimal ? encoded.slice(1) : encoded, hexadecimal ? 16 : 10);
|
|
50
|
+
if (codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343) return entity;
|
|
51
|
+
return String.fromCodePoint(codePoint);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function redact(value, secrets) {
|
|
55
|
+
return secrets.reduce((result, secret) => result.replaceAll(secret, "[redacted]"), value);
|
|
56
|
+
}
|
|
57
|
+
function apiErrorMessage(body, secrets) {
|
|
58
|
+
if (body.length === 0) return void 0;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(body);
|
|
61
|
+
if (isRecord(parsed) && isRecord(parsed.error) && typeof parsed.error.message === "string") {
|
|
62
|
+
return redact(parsed.error.message, secrets);
|
|
63
|
+
}
|
|
64
|
+
if (isRecord(parsed) && typeof parsed.message === "string") return redact(parsed.message, secrets);
|
|
65
|
+
} catch {
|
|
66
|
+
return redact(body, secrets);
|
|
67
|
+
}
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
function parseEndpoint(value) {
|
|
71
|
+
try {
|
|
72
|
+
return new URL(value ?? DEFAULT_ENDPOINT).toString();
|
|
73
|
+
} catch (cause) {
|
|
74
|
+
throw new TypeError("apiEndpoint must be a valid absolute URL.", { cause });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function createGoogleTranslator(options) {
|
|
78
|
+
const hasApiKey = options?.apiKey !== void 0;
|
|
79
|
+
const hasTokenProvider = options?.getAccessToken !== void 0;
|
|
80
|
+
if (hasApiKey === hasTokenProvider) {
|
|
81
|
+
throw new TypeError("Provide exactly one of apiKey or getAccessToken.");
|
|
82
|
+
}
|
|
83
|
+
const apiKey = hasApiKey ? requireNonEmpty(options.apiKey, "apiKey") : void 0;
|
|
84
|
+
if (hasTokenProvider && typeof options.getAccessToken !== "function") {
|
|
85
|
+
throw new TypeError("getAccessToken must be a function.");
|
|
86
|
+
}
|
|
87
|
+
const getAccessToken = options.getAccessToken;
|
|
88
|
+
const endpoint = parseEndpoint(options.apiEndpoint);
|
|
89
|
+
const fetchImpl = options.fetchFn ?? globalThis.fetch;
|
|
90
|
+
if (typeof fetchImpl !== "function") {
|
|
91
|
+
throw new Error("Fetch is unavailable. Pass fetchFn when creating the Google translator.");
|
|
92
|
+
}
|
|
93
|
+
const translateBatch = async (texts, targetLocale, sourceLocale) => {
|
|
94
|
+
if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
|
|
95
|
+
throw new TypeError("texts must be an array of strings.");
|
|
96
|
+
}
|
|
97
|
+
if (texts.length > MAX_BATCH_SIZE) {
|
|
98
|
+
throw new TypeError(`texts must contain no more than ${MAX_BATCH_SIZE} items.`);
|
|
99
|
+
}
|
|
100
|
+
const target = requireNonEmpty(targetLocale, "targetLocale");
|
|
101
|
+
const source = sourceLocale === void 0 ? void 0 : requireNonEmpty(sourceLocale, "sourceLocale");
|
|
102
|
+
if (texts.length === 0) return [];
|
|
103
|
+
const requestUrl = new URL(endpoint);
|
|
104
|
+
const headers = { "Content-Type": "application/json" };
|
|
105
|
+
const secrets = [];
|
|
106
|
+
if (apiKey !== void 0) {
|
|
107
|
+
requestUrl.searchParams.set("key", apiKey);
|
|
108
|
+
secrets.push(apiKey);
|
|
109
|
+
} else {
|
|
110
|
+
if (getAccessToken === void 0) throw new Error("Google access token provider is unavailable.");
|
|
111
|
+
let providedToken;
|
|
112
|
+
try {
|
|
113
|
+
providedToken = await getAccessToken();
|
|
114
|
+
} catch (cause) {
|
|
115
|
+
throw new Error("Google access token provider failed.", { cause });
|
|
116
|
+
}
|
|
117
|
+
const token = requireNonEmpty(providedToken, "accessToken");
|
|
118
|
+
headers.Authorization = `Bearer ${token}`;
|
|
119
|
+
secrets.push(token);
|
|
120
|
+
}
|
|
121
|
+
let response;
|
|
122
|
+
try {
|
|
123
|
+
response = await fetchImpl(requestUrl.toString(), {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers,
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
q: texts,
|
|
128
|
+
target,
|
|
129
|
+
format: "text",
|
|
130
|
+
...source === void 0 ? {} : { source }
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
} catch (cause) {
|
|
134
|
+
throw new Error("Google Translation request failed before receiving an HTTP response.", { cause });
|
|
135
|
+
}
|
|
136
|
+
let body;
|
|
137
|
+
try {
|
|
138
|
+
body = await response.text();
|
|
139
|
+
} catch (cause) {
|
|
140
|
+
throw new Error(`Google Translation response body could not be read (HTTP ${response.status}).`, { cause });
|
|
141
|
+
}
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const detail = apiErrorMessage(body, secrets);
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Google Translation request failed with HTTP ${response.status}${detail === void 0 ? "." : `: ${detail}`}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
let parsed;
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(body);
|
|
151
|
+
} catch (cause) {
|
|
152
|
+
throw new Error(`Google Translation returned invalid JSON (HTTP ${response.status}).`, { cause });
|
|
153
|
+
}
|
|
154
|
+
const translations = isRecord(parsed) && isRecord(parsed.data) && Array.isArray(parsed.data.translations) ? parsed.data.translations : void 0;
|
|
155
|
+
if (translations === void 0) {
|
|
156
|
+
throw new Error("Google Translation response is missing the data.translations array.");
|
|
157
|
+
}
|
|
158
|
+
if (translations.length !== texts.length) {
|
|
159
|
+
throw new Error(`Google Translation returned ${translations.length} translations for ${texts.length} texts.`);
|
|
160
|
+
}
|
|
161
|
+
return translations.map((translation, index) => {
|
|
162
|
+
if (!isRecord(translation) || typeof translation.translatedText !== "string") {
|
|
163
|
+
throw new Error(`Google Translation result at index ${index} is invalid.`);
|
|
164
|
+
}
|
|
165
|
+
return decodeHtmlEntities(translation.translatedText);
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
async translateText(text, targetLocale, sourceLocale) {
|
|
170
|
+
if (typeof text !== "string") throw new TypeError("text must be a string.");
|
|
171
|
+
const translations = await translateBatch([text], targetLocale, sourceLocale);
|
|
172
|
+
const translated = translations[0];
|
|
173
|
+
if (translated === void 0) throw new Error("Google Translation returned no translation.");
|
|
174
|
+
return translated;
|
|
175
|
+
},
|
|
176
|
+
translateBatch
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
180
|
+
0 && (module.exports = {
|
|
181
|
+
createGoogleTranslator
|
|
182
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AsyncTranslationAdapter } from '@form-engine-ts/core';
|
|
2
|
+
|
|
3
|
+
interface GoogleTranslatorOptions {
|
|
4
|
+
readonly apiKey?: string;
|
|
5
|
+
readonly getAccessToken?: () => Promise<string> | string;
|
|
6
|
+
readonly fetchFn?: typeof fetch;
|
|
7
|
+
readonly apiEndpoint?: string;
|
|
8
|
+
}
|
|
9
|
+
declare function createGoogleTranslator(options: GoogleTranslatorOptions): AsyncTranslationAdapter;
|
|
10
|
+
|
|
11
|
+
export { type GoogleTranslatorOptions, createGoogleTranslator };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AsyncTranslationAdapter } from '@form-engine-ts/core';
|
|
2
|
+
|
|
3
|
+
interface GoogleTranslatorOptions {
|
|
4
|
+
readonly apiKey?: string;
|
|
5
|
+
readonly getAccessToken?: () => Promise<string> | string;
|
|
6
|
+
readonly fetchFn?: typeof fetch;
|
|
7
|
+
readonly apiEndpoint?: string;
|
|
8
|
+
}
|
|
9
|
+
declare function createGoogleTranslator(options: GoogleTranslatorOptions): AsyncTranslationAdapter;
|
|
10
|
+
|
|
11
|
+
export { type GoogleTranslatorOptions, createGoogleTranslator };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://translation.googleapis.com/language/translate/v2";
|
|
3
|
+
var MAX_BATCH_SIZE = 128;
|
|
4
|
+
var NAMED_ENTITIES = {
|
|
5
|
+
quot: '"',
|
|
6
|
+
apos: "'",
|
|
7
|
+
amp: "&",
|
|
8
|
+
lt: "<",
|
|
9
|
+
gt: ">"
|
|
10
|
+
};
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function requireNonEmpty(value, name) {
|
|
15
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
16
|
+
throw new TypeError(`${name} must be a non-empty string.`);
|
|
17
|
+
}
|
|
18
|
+
return value.trim();
|
|
19
|
+
}
|
|
20
|
+
function decodeHtmlEntities(text) {
|
|
21
|
+
return text.replace(/&(?:quot|apos|amp|lt|gt|#(?:[xX][0-9a-fA-F]+|[0-9]+));/g, (entity) => {
|
|
22
|
+
if (!entity.startsWith("&#")) return NAMED_ENTITIES[entity.slice(1, -1)] ?? entity;
|
|
23
|
+
const encoded = entity.slice(2, -1);
|
|
24
|
+
const hexadecimal = encoded.startsWith("x") || encoded.startsWith("X");
|
|
25
|
+
const codePoint = Number.parseInt(hexadecimal ? encoded.slice(1) : encoded, hexadecimal ? 16 : 10);
|
|
26
|
+
if (codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343) return entity;
|
|
27
|
+
return String.fromCodePoint(codePoint);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function redact(value, secrets) {
|
|
31
|
+
return secrets.reduce((result, secret) => result.replaceAll(secret, "[redacted]"), value);
|
|
32
|
+
}
|
|
33
|
+
function apiErrorMessage(body, secrets) {
|
|
34
|
+
if (body.length === 0) return void 0;
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(body);
|
|
37
|
+
if (isRecord(parsed) && isRecord(parsed.error) && typeof parsed.error.message === "string") {
|
|
38
|
+
return redact(parsed.error.message, secrets);
|
|
39
|
+
}
|
|
40
|
+
if (isRecord(parsed) && typeof parsed.message === "string") return redact(parsed.message, secrets);
|
|
41
|
+
} catch {
|
|
42
|
+
return redact(body, secrets);
|
|
43
|
+
}
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
function parseEndpoint(value) {
|
|
47
|
+
try {
|
|
48
|
+
return new URL(value ?? DEFAULT_ENDPOINT).toString();
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
throw new TypeError("apiEndpoint must be a valid absolute URL.", { cause });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function createGoogleTranslator(options) {
|
|
54
|
+
const hasApiKey = options?.apiKey !== void 0;
|
|
55
|
+
const hasTokenProvider = options?.getAccessToken !== void 0;
|
|
56
|
+
if (hasApiKey === hasTokenProvider) {
|
|
57
|
+
throw new TypeError("Provide exactly one of apiKey or getAccessToken.");
|
|
58
|
+
}
|
|
59
|
+
const apiKey = hasApiKey ? requireNonEmpty(options.apiKey, "apiKey") : void 0;
|
|
60
|
+
if (hasTokenProvider && typeof options.getAccessToken !== "function") {
|
|
61
|
+
throw new TypeError("getAccessToken must be a function.");
|
|
62
|
+
}
|
|
63
|
+
const getAccessToken = options.getAccessToken;
|
|
64
|
+
const endpoint = parseEndpoint(options.apiEndpoint);
|
|
65
|
+
const fetchImpl = options.fetchFn ?? globalThis.fetch;
|
|
66
|
+
if (typeof fetchImpl !== "function") {
|
|
67
|
+
throw new Error("Fetch is unavailable. Pass fetchFn when creating the Google translator.");
|
|
68
|
+
}
|
|
69
|
+
const translateBatch = async (texts, targetLocale, sourceLocale) => {
|
|
70
|
+
if (!Array.isArray(texts) || texts.some((text) => typeof text !== "string")) {
|
|
71
|
+
throw new TypeError("texts must be an array of strings.");
|
|
72
|
+
}
|
|
73
|
+
if (texts.length > MAX_BATCH_SIZE) {
|
|
74
|
+
throw new TypeError(`texts must contain no more than ${MAX_BATCH_SIZE} items.`);
|
|
75
|
+
}
|
|
76
|
+
const target = requireNonEmpty(targetLocale, "targetLocale");
|
|
77
|
+
const source = sourceLocale === void 0 ? void 0 : requireNonEmpty(sourceLocale, "sourceLocale");
|
|
78
|
+
if (texts.length === 0) return [];
|
|
79
|
+
const requestUrl = new URL(endpoint);
|
|
80
|
+
const headers = { "Content-Type": "application/json" };
|
|
81
|
+
const secrets = [];
|
|
82
|
+
if (apiKey !== void 0) {
|
|
83
|
+
requestUrl.searchParams.set("key", apiKey);
|
|
84
|
+
secrets.push(apiKey);
|
|
85
|
+
} else {
|
|
86
|
+
if (getAccessToken === void 0) throw new Error("Google access token provider is unavailable.");
|
|
87
|
+
let providedToken;
|
|
88
|
+
try {
|
|
89
|
+
providedToken = await getAccessToken();
|
|
90
|
+
} catch (cause) {
|
|
91
|
+
throw new Error("Google access token provider failed.", { cause });
|
|
92
|
+
}
|
|
93
|
+
const token = requireNonEmpty(providedToken, "accessToken");
|
|
94
|
+
headers.Authorization = `Bearer ${token}`;
|
|
95
|
+
secrets.push(token);
|
|
96
|
+
}
|
|
97
|
+
let response;
|
|
98
|
+
try {
|
|
99
|
+
response = await fetchImpl(requestUrl.toString(), {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers,
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
q: texts,
|
|
104
|
+
target,
|
|
105
|
+
format: "text",
|
|
106
|
+
...source === void 0 ? {} : { source }
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw new Error("Google Translation request failed before receiving an HTTP response.", { cause });
|
|
111
|
+
}
|
|
112
|
+
let body;
|
|
113
|
+
try {
|
|
114
|
+
body = await response.text();
|
|
115
|
+
} catch (cause) {
|
|
116
|
+
throw new Error(`Google Translation response body could not be read (HTTP ${response.status}).`, { cause });
|
|
117
|
+
}
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const detail = apiErrorMessage(body, secrets);
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Google Translation request failed with HTTP ${response.status}${detail === void 0 ? "." : `: ${detail}`}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
let parsed;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(body);
|
|
127
|
+
} catch (cause) {
|
|
128
|
+
throw new Error(`Google Translation returned invalid JSON (HTTP ${response.status}).`, { cause });
|
|
129
|
+
}
|
|
130
|
+
const translations = isRecord(parsed) && isRecord(parsed.data) && Array.isArray(parsed.data.translations) ? parsed.data.translations : void 0;
|
|
131
|
+
if (translations === void 0) {
|
|
132
|
+
throw new Error("Google Translation response is missing the data.translations array.");
|
|
133
|
+
}
|
|
134
|
+
if (translations.length !== texts.length) {
|
|
135
|
+
throw new Error(`Google Translation returned ${translations.length} translations for ${texts.length} texts.`);
|
|
136
|
+
}
|
|
137
|
+
return translations.map((translation, index) => {
|
|
138
|
+
if (!isRecord(translation) || typeof translation.translatedText !== "string") {
|
|
139
|
+
throw new Error(`Google Translation result at index ${index} is invalid.`);
|
|
140
|
+
}
|
|
141
|
+
return decodeHtmlEntities(translation.translatedText);
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
async translateText(text, targetLocale, sourceLocale) {
|
|
146
|
+
if (typeof text !== "string") throw new TypeError("text must be a string.");
|
|
147
|
+
const translations = await translateBatch([text], targetLocale, sourceLocale);
|
|
148
|
+
const translated = translations[0];
|
|
149
|
+
if (translated === void 0) throw new Error("Google Translation returned no translation.");
|
|
150
|
+
return translated;
|
|
151
|
+
},
|
|
152
|
+
translateBatch
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export {
|
|
156
|
+
createGoogleTranslator
|
|
157
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@form-engine-ts/translator-google",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/nitta-a/form-engine-ts.git",
|
|
28
|
+
"directory": "packages/translator-google"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/nitta-a/form-engine-ts/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/nitta-a/form-engine-ts#readme",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"form",
|
|
36
|
+
"translation",
|
|
37
|
+
"google-cloud",
|
|
38
|
+
"translator",
|
|
39
|
+
"typescript"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@form-engine-ts/core": "1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|
|
46
|
+
"check": "biome check . && tsc --noEmit",
|
|
47
|
+
"test": "vitest run --globals",
|
|
48
|
+
"typecheck": "tsc --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|