@ecosplay/e-translate 1.0.0 → 1.1.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 +55 -40
- package/dist/index.cjs +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
- package/src/index.ts +12 -12
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
[](https://sn.e-cosplay.fr/dashboard?id=e-translate-sdk-js)
|
|
10
10
|
[](https://sn.e-cosplay.fr/dashboard?id=e-translate-sdk-js)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
(
|
|
12
|
+
**TypeScript / JavaScript** client for the [e-translate](https://translation.e-cosplay.fr) API
|
|
13
|
+
(translation through a key-authenticated API, with built-in caching).
|
|
14
14
|
|
|
15
|
-
- ✅
|
|
16
|
-
- ✅ **TypeScript** (types
|
|
17
|
-
- ✅
|
|
18
|
-
- ✅
|
|
15
|
+
- ✅ Works in **Node.js ≥ 18** (native fetch) and in the **browser**
|
|
16
|
+
- ✅ **TypeScript** (types included) — **ESM + CJS** outputs
|
|
17
|
+
- ✅ Zero runtime dependencies
|
|
18
|
+
- ✅ Available on **npm** (`@ecosplay/e-translate`)
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -25,25 +25,25 @@ Client **TypeScript / JavaScript** pour l'API [e-translate](https://translation.
|
|
|
25
25
|
npm install @ecosplay/e-translate
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
With pnpm or yarn:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
pnpm add @ecosplay/e-translate
|
|
32
32
|
yarn add @ecosplay/e-translate
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
**Pin a version** (recommended in production):
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
npm install @ecosplay/e-translate@1.
|
|
38
|
+
npm install @ecosplay/e-translate@1.1.0
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
>
|
|
41
|
+
> Published to npm automatically by CI on each `vX.Y.Z` tag (see
|
|
42
42
|
> `.gitea/workflows/publish.yml`).
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
## 🚀
|
|
46
|
+
## 🚀 Usage
|
|
47
47
|
|
|
48
48
|
### ESM / TypeScript
|
|
49
49
|
|
|
@@ -57,7 +57,7 @@ const client = new ETranslateClient({
|
|
|
57
57
|
|
|
58
58
|
const res = await client.translate({ message: "Bonjour le monde", from: "fr", to: "en" });
|
|
59
59
|
console.log(res.translatedText); // "Hello world"
|
|
60
|
-
console.log(res.cached); // false
|
|
60
|
+
console.log(res.cached); // false, then true on an identical 2nd call
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
### CommonJS
|
|
@@ -73,18 +73,18 @@ const client = new ETranslateClient({
|
|
|
73
73
|
client.translate({ message: "Hello", from: "en", to: "fr" }).then((r) => console.log(r.translatedText));
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
###
|
|
76
|
+
### Auto-detection, arrays, HTML
|
|
77
77
|
|
|
78
78
|
```ts
|
|
79
|
-
// from: "auto" ->
|
|
79
|
+
// from: "auto" -> the source language is detected
|
|
80
80
|
const r1 = await client.translate({ message: "Guten Tag", from: "auto", to: "fr" });
|
|
81
81
|
console.log(r1.detectedLanguage); // { confidence: 90, language: "de" }
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// Translate several texts at once
|
|
84
84
|
const r2 = await client.translate({ message: ["Cat", "Dog"], from: "en", to: "fr" });
|
|
85
85
|
console.log(r2.translatedText); // ["Chat", "Chien"]
|
|
86
86
|
|
|
87
|
-
//
|
|
87
|
+
// Preserve HTML markup
|
|
88
88
|
await client.translate({ message: "<b>Bonjour</b>", from: "fr", to: "en", format: "html" });
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -94,29 +94,29 @@ await client.translate({ message: "<b>Bonjour</b>", from: "fr", to: "en", format
|
|
|
94
94
|
|
|
95
95
|
### `new ETranslateClient(options)` / `createClient(options)`
|
|
96
96
|
|
|
97
|
-
| Option | Type |
|
|
98
|
-
| ----------- | ------------------------ | ------------------ |
|
|
99
|
-
| `baseUrl` | `string` | — | URL
|
|
100
|
-
| `apiKey` | `string` | — |
|
|
101
|
-
| `timeoutMs` | `number` | `20000` |
|
|
102
|
-
| `fetch` | `typeof fetch` | `globalThis.fetch` |
|
|
103
|
-
| `headers` | `Record<string,string>` | `{}` |
|
|
97
|
+
| Option | Type | Default | Description |
|
|
98
|
+
| ----------- | ------------------------ | ------------------ | ---------------------------------------- |
|
|
99
|
+
| `baseUrl` | `string` | — | Server URL (required) |
|
|
100
|
+
| `apiKey` | `string` | — | `etk_…` key (required except `health()`) |
|
|
101
|
+
| `timeoutMs` | `number` | `20000` | Per-request timeout |
|
|
102
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch (Node < 18, tests) |
|
|
103
|
+
| `headers` | `Record<string,string>` | `{}` | Extra headers |
|
|
104
104
|
|
|
105
|
-
###
|
|
105
|
+
### Methods
|
|
106
106
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
| `translate(params)`
|
|
110
|
-
| `detect(text)`
|
|
111
|
-
| `languages()`
|
|
112
|
-
| `health()`
|
|
107
|
+
| Method | Endpoint | Returns |
|
|
108
|
+
| --------------------- | ----------------- | ----------------------------- |
|
|
109
|
+
| `translate(params)` | `POST /translate` | `Promise<TranslateResult>` |
|
|
110
|
+
| `detect(text)` | `POST /detect` | `Promise<DetectedLanguage[]>` |
|
|
111
|
+
| `languages()` | `GET /languages` | `Promise<Language[]>` |
|
|
112
|
+
| `health()` | `GET /health` | `Promise<HealthResult>` |
|
|
113
113
|
|
|
114
|
-
`translate(params)` — `params
|
|
115
|
-
(`from`
|
|
114
|
+
`translate(params)` — `params`: `{ message: string | string[]; from: string; to: string; format?: "text" | "html" }`.
|
|
115
|
+
(`from` accepts `"auto"`. The server-side aliases `source`/`target`/`q` are also accepted.)
|
|
116
116
|
|
|
117
|
-
###
|
|
117
|
+
### Error handling
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Any non-2xx response (or a network failure) throws an **`ETranslateError`**:
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
122
|
import { ETranslateError } from "@ecosplay/e-translate";
|
|
@@ -125,25 +125,40 @@ try {
|
|
|
125
125
|
await client.translate({ message: "x", from: "fr", to: "en" });
|
|
126
126
|
} catch (e) {
|
|
127
127
|
if (e instanceof ETranslateError) {
|
|
128
|
-
console.error(e.status); // 401 (
|
|
128
|
+
console.error(e.status); // 401 (invalid key), 429 (rate limit), 502 (upstream), 0 (network/timeout)
|
|
129
129
|
console.error(e.message);
|
|
130
|
-
console.error(e.body); //
|
|
130
|
+
console.error(e.body); // JSON body returned by the server, if any
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
137
|
-
##
|
|
137
|
+
## 🧪 Tests & coverage
|
|
138
|
+
|
|
139
|
+
Tests run against an in-process **mock server** (`test/mock-server.ts`), so no real
|
|
140
|
+
backend is required.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm test # run the test suite (vitest)
|
|
144
|
+
npm run test:coverage # run with V8 coverage -> coverage/lcov.info (used by SonarQube)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Coverage is reported to [SonarQube](https://sn.e-cosplay.fr/dashboard?id=e-translate-sdk-js)
|
|
148
|
+
by CI (see the `Coverage` badge above).
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 🛠️ Build from source
|
|
138
153
|
|
|
139
154
|
```bash
|
|
140
155
|
git clone ssh://git@code.e-cosplay.fr:222/shoko/e-translate-sdk-js.git
|
|
141
156
|
cd e-translate-sdk-js
|
|
142
|
-
npm install #
|
|
157
|
+
npm install # also runs the build via "prepare"
|
|
143
158
|
npm run build # -> dist/index.js (ESM), dist/index.cjs (CJS), dist/index.d.ts
|
|
144
|
-
npm run typecheck #
|
|
159
|
+
npm run typecheck # type-check without emitting
|
|
145
160
|
```
|
|
146
161
|
|
|
147
|
-
##
|
|
162
|
+
## License
|
|
148
163
|
|
|
149
164
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ var ETranslateError = class _ETranslateError extends Error {
|
|
|
38
38
|
var ETranslateClient = class {
|
|
39
39
|
constructor(options) {
|
|
40
40
|
if (!options || typeof options.baseUrl !== "string" || options.baseUrl.length === 0) {
|
|
41
|
-
throw new Error("ETranslate:
|
|
41
|
+
throw new Error("ETranslate: the 'baseUrl' option is required.");
|
|
42
42
|
}
|
|
43
43
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
44
44
|
this.apiKey = options.apiKey ?? "";
|
|
@@ -46,8 +46,8 @@ var ETranslateClient = class {
|
|
|
46
46
|
this.extraHeaders = options.headers ?? {};
|
|
47
47
|
const f = options.fetch ?? globalThis.fetch;
|
|
48
48
|
if (typeof f !== "function") {
|
|
49
|
-
throw new
|
|
50
|
-
"ETranslate:
|
|
49
|
+
throw new TypeError(
|
|
50
|
+
"ETranslate: no `fetch` implementation available. Use Node >= 18 or pass `options.fetch`."
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
this.fetchImpl = f;
|
|
@@ -57,11 +57,11 @@ var ETranslateClient = class {
|
|
|
57
57
|
* Envoie `lang_src` / `lang_dest` / `message` à `POST /translate`.
|
|
58
58
|
*/
|
|
59
59
|
async translate(params) {
|
|
60
|
-
if (
|
|
61
|
-
throw new Error("translate: 'message'
|
|
60
|
+
if (params?.message == null) {
|
|
61
|
+
throw new Error("translate: 'message' is required.");
|
|
62
62
|
}
|
|
63
|
-
if (!params.from) throw new Error("translate: 'from'
|
|
64
|
-
if (!params.to) throw new Error("translate: 'to'
|
|
63
|
+
if (!params.from) throw new Error("translate: 'from' is required (e.g. 'fr' or 'auto').");
|
|
64
|
+
if (!params.to) throw new Error("translate: 'to' is required (e.g. 'en').");
|
|
65
65
|
return this.request("POST", "/translate", {
|
|
66
66
|
lang_src: params.from,
|
|
67
67
|
lang_dest: params.to,
|
|
@@ -71,7 +71,7 @@ var ETranslateClient = class {
|
|
|
71
71
|
}
|
|
72
72
|
/** Détecte la langue d'un texte via `POST /detect`. */
|
|
73
73
|
async detect(text) {
|
|
74
|
-
if (!text) throw new Error("detect: 'text'
|
|
74
|
+
if (!text) throw new Error("detect: 'text' is required.");
|
|
75
75
|
return this.request("POST", "/detect", { q: text });
|
|
76
76
|
}
|
|
77
77
|
/** Liste les langues supportées via `GET /languages`. */
|
|
@@ -95,12 +95,12 @@ var ETranslateClient = class {
|
|
|
95
95
|
resp = await this.fetchImpl(url, {
|
|
96
96
|
method,
|
|
97
97
|
headers,
|
|
98
|
-
body: body
|
|
98
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
99
99
|
signal: controller.signal
|
|
100
100
|
});
|
|
101
101
|
} catch (err) {
|
|
102
102
|
const e = err;
|
|
103
|
-
const msg = e?.name === "AbortError" ? `
|
|
103
|
+
const msg = e?.name === "AbortError" ? `Request timed out after ${this.timeoutMs} ms (${path}).` : `Network error to ${url}: ${e?.message ?? String(err)}`;
|
|
104
104
|
throw new ETranslateError(msg, 0);
|
|
105
105
|
} finally {
|
|
106
106
|
clearTimeout(timer);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * e-translate SDK — client TypeScript/JavaScript pour l'API e-translate.\n *\n * Fonctionne dans Node 18+ (fetch global) et dans le navigateur.\n *\n * @example\n * ```ts\n * import { ETranslateClient } from \"e-translate-sdk\";\n *\n * const client = new ETranslateClient({\n * baseUrl: \"https://translation.e-cosplay.fr\",\n * apiKey: \"etk_xxxxxxxx\",\n * });\n *\n * const res = await client.translate({ message: \"Bonjour\", from: \"fr\", to: \"en\" });\n * console.log(res.translatedText); // \"Hello\"\n * ```\n */\n\n/** Format du texte à traduire. */\nexport type TranslateFormat = \"text\" | \"html\";\n\n/** Méthode HTTP interne. */\ntype HttpMethod = \"GET\" | \"POST\";\n\n/** Options de construction du client. */\nexport interface ETranslateOptions {\n /** URL de base du serveur, ex. \"https://translation.e-cosplay.fr\". */\n baseUrl: string;\n /** Clé d'API (`etk_...`). Requise pour /translate, /detect, /languages. */\n apiKey: string;\n /** Timeout par requête en millisecondes (défaut : 20000). */\n timeoutMs?: number;\n /** Implémentation `fetch` personnalisée (Node < 18, tests). Défaut : `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** En-têtes HTTP supplémentaires envoyés à chaque requête. */\n headers?: Record<string, string>;\n}\n\n/** Paramètres de traduction. */\nexport interface TranslateParams {\n /** Texte (ou tableau de textes) à traduire. */\n message: string | string[];\n /** Langue source (code ISO, ex. \"fr\") ou \"auto\" pour la détection automatique. */\n from: string;\n /** Langue cible (code ISO, ex. \"en\"). */\n to: string;\n /** Format du texte (défaut : \"text\"). */\n format?: TranslateFormat;\n}\n\n/** Langue détectée renvoyée par /detect ou en mode \"auto\". */\nexport interface DetectedLanguage {\n /** Indice de confiance (0–100). */\n confidence: number;\n /** Code ISO de la langue détectée. */\n language: string;\n}\n\n/** Résultat d'une traduction. */\nexport interface TranslateResult {\n /** Texte traduit (string si `message` était une string, sinon string[]). */\n translatedText: string | string[];\n /** `true` si la réponse provient du cache Redis. */\n cached: boolean;\n /** Présent uniquement si `from` valait \"auto\". */\n detectedLanguage?: DetectedLanguage;\n}\n\n/** Une langue supportée par le serveur. */\nexport interface Language {\n code: string;\n name: string;\n targets?: string[];\n}\n\n/** Réponse de /health (aucune clé requise). */\nexport interface HealthResult {\n status: string;\n redis: boolean;\n /** Disponibilité du moteur de traduction en amont. */\n engine: boolean;\n time: string;\n}\n\n/**\n * Erreur levée pour toute réponse non-2xx ou tout échec réseau.\n * `status` vaut 0 en cas d'échec réseau / timeout.\n */\nexport class ETranslateError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ETranslateError\";\n this.status = status;\n this.body = body;\n // Restaure la chaîne de prototype (TS ciblant ES5/ES2015).\n Object.setPrototypeOf(this, ETranslateError.prototype);\n }\n}\n\n/** Client de l'API e-translate. */\nexport class ETranslateClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly extraHeaders: Record<string, string>;\n\n constructor(options: ETranslateOptions) {\n if (!options || typeof options.baseUrl !== \"string\" || options.baseUrl.length === 0) {\n throw new Error(\"ETranslate: l'option 'baseUrl' est requise.\");\n }\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = options.apiKey ?? \"\";\n this.timeoutMs = options.timeoutMs ?? 20000;\n this.extraHeaders = options.headers ?? {};\n\n const f = options.fetch ?? (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof f !== \"function\") {\n throw new Error(\n \"ETranslate: aucune implémentation `fetch` disponible. \" +\n \"Utilisez Node >= 18 ou passez `options.fetch`.\",\n );\n }\n this.fetchImpl = f;\n }\n\n /**\n * Traduit un texte (ou un tableau de textes).\n * Envoie `lang_src` / `lang_dest` / `message` à `POST /translate`.\n */\n async translate(params: TranslateParams): Promise<TranslateResult> {\n if (!params || params.message == null) {\n throw new Error(\"translate: 'message' est requis.\");\n }\n if (!params.from) throw new Error(\"translate: 'from' est requis (ex. 'fr' ou 'auto').\");\n if (!params.to) throw new Error(\"translate: 'to' est requis (ex. 'en').\");\n\n return this.request<TranslateResult>(\"POST\", \"/translate\", {\n lang_src: params.from,\n lang_dest: params.to,\n message: params.message,\n format: params.format ?? \"text\",\n });\n }\n\n /** Détecte la langue d'un texte via `POST /detect`. */\n async detect(text: string): Promise<DetectedLanguage[]> {\n if (!text) throw new Error(\"detect: 'text' est requis.\");\n return this.request<DetectedLanguage[]>(\"POST\", \"/detect\", { q: text });\n }\n\n /** Liste les langues supportées via `GET /languages`. */\n async languages(): Promise<Language[]> {\n return this.request<Language[]>(\"GET\", \"/languages\");\n }\n\n /** État du service via `GET /health` (ne nécessite pas de clé API). */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>(\"GET\", \"/health\", undefined, { auth: false });\n }\n\n private async request<T>(\n method: HttpMethod,\n path: string,\n body?: Record<string, unknown>,\n opts?: { auth?: boolean },\n ): Promise<T> {\n const useAuth = opts?.auth !== false;\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = { Accept: \"application/json\", ...this.extraHeaders };\n if (useAuth) headers[\"X-API-Key\"] = this.apiKey;\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this.fetchImpl(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n const e = err as { name?: string; message?: string };\n const msg =\n e?.name === \"AbortError\"\n ? `Requête expirée après ${this.timeoutMs} ms (${path}).`\n : `Échec réseau vers ${url}: ${e?.message ?? String(err)}`;\n throw new ETranslateError(msg, 0);\n } finally {\n clearTimeout(timer);\n }\n\n const raw = await resp.text();\n let data: unknown = undefined;\n if (raw) {\n try {\n data = JSON.parse(raw);\n } catch {\n data = raw;\n }\n }\n\n if (!resp.ok) {\n const message =\n data && typeof data === \"object\" && \"error\" in data\n ? String((data as { error: unknown }).error)\n : `HTTP ${resp.status} sur ${path}`;\n throw new ETranslateError(message, resp.status, data);\n }\n\n return data as T;\n }\n}\n\n/** Fabrique pratique équivalente à `new ETranslateClient(options)`. */\nexport function createClient(options: ETranslateOptions): ETranslateClient {\n return new ETranslateClient(options);\n}\n\nexport default ETranslateClient;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * e-translate SDK — client TypeScript/JavaScript pour l'API e-translate.\n *\n * Fonctionne dans Node 18+ (fetch global) et dans le navigateur.\n *\n * @example\n * ```ts\n * import { ETranslateClient } from \"e-translate-sdk\";\n *\n * const client = new ETranslateClient({\n * baseUrl: \"https://translation.e-cosplay.fr\",\n * apiKey: \"etk_xxxxxxxx\",\n * });\n *\n * const res = await client.translate({ message: \"Bonjour\", from: \"fr\", to: \"en\" });\n * console.log(res.translatedText); // \"Hello\"\n * ```\n */\n\n/** Format du texte à traduire. */\nexport type TranslateFormat = \"text\" | \"html\";\n\n/** Méthode HTTP interne. */\ntype HttpMethod = \"GET\" | \"POST\";\n\n/** Options de construction du client. */\nexport interface ETranslateOptions {\n /** URL de base du serveur, ex. \"https://translation.e-cosplay.fr\". */\n baseUrl: string;\n /** Clé d'API (`etk_...`). Requise pour /translate, /detect, /languages. */\n apiKey: string;\n /** Timeout par requête en millisecondes (défaut : 20000). */\n timeoutMs?: number;\n /** Implémentation `fetch` personnalisée (Node < 18, tests). Défaut : `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** En-têtes HTTP supplémentaires envoyés à chaque requête. */\n headers?: Record<string, string>;\n}\n\n/** Paramètres de traduction. */\nexport interface TranslateParams {\n /** Texte (ou tableau de textes) à traduire. */\n message: string | string[];\n /** Langue source (code ISO, ex. \"fr\") ou \"auto\" pour la détection automatique. */\n from: string;\n /** Langue cible (code ISO, ex. \"en\"). */\n to: string;\n /** Format du texte (défaut : \"text\"). */\n format?: TranslateFormat;\n}\n\n/** Langue détectée renvoyée par /detect ou en mode \"auto\". */\nexport interface DetectedLanguage {\n /** Indice de confiance (0–100). */\n confidence: number;\n /** Code ISO de la langue détectée. */\n language: string;\n}\n\n/** Résultat d'une traduction. */\nexport interface TranslateResult {\n /** Texte traduit (string si `message` était une string, sinon string[]). */\n translatedText: string | string[];\n /** `true` si la réponse provient du cache Redis. */\n cached: boolean;\n /** Présent uniquement si `from` valait \"auto\". */\n detectedLanguage?: DetectedLanguage;\n}\n\n/** Une langue supportée par le serveur. */\nexport interface Language {\n code: string;\n name: string;\n targets?: string[];\n}\n\n/** Réponse de /health (aucune clé requise). */\nexport interface HealthResult {\n status: string;\n redis: boolean;\n /** Disponibilité du moteur de traduction en amont. */\n engine: boolean;\n time: string;\n}\n\n/**\n * Erreur levée pour toute réponse non-2xx ou tout échec réseau.\n * `status` vaut 0 en cas d'échec réseau / timeout.\n */\nexport class ETranslateError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ETranslateError\";\n this.status = status;\n this.body = body;\n // Restaure la chaîne de prototype (TS ciblant ES5/ES2015).\n Object.setPrototypeOf(this, ETranslateError.prototype);\n }\n}\n\n/** Client de l'API e-translate. */\nexport class ETranslateClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly extraHeaders: Record<string, string>;\n\n constructor(options: ETranslateOptions) {\n if (!options || typeof options.baseUrl !== \"string\" || options.baseUrl.length === 0) {\n throw new Error(\"ETranslate: the 'baseUrl' option is required.\");\n }\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = options.apiKey ?? \"\";\n this.timeoutMs = options.timeoutMs ?? 20000;\n this.extraHeaders = options.headers ?? {};\n\n const f = options.fetch ?? (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof f !== \"function\") {\n throw new TypeError(\n \"ETranslate: no `fetch` implementation available. \" +\n \"Use Node >= 18 or pass `options.fetch`.\",\n );\n }\n this.fetchImpl = f;\n }\n\n /**\n * Traduit un texte (ou un tableau de textes).\n * Envoie `lang_src` / `lang_dest` / `message` à `POST /translate`.\n */\n async translate(params: TranslateParams): Promise<TranslateResult> {\n if (params?.message == null) {\n throw new Error(\"translate: 'message' is required.\");\n }\n if (!params.from) throw new Error(\"translate: 'from' is required (e.g. 'fr' or 'auto').\");\n if (!params.to) throw new Error(\"translate: 'to' is required (e.g. 'en').\");\n\n return this.request<TranslateResult>(\"POST\", \"/translate\", {\n lang_src: params.from,\n lang_dest: params.to,\n message: params.message,\n format: params.format ?? \"text\",\n });\n }\n\n /** Détecte la langue d'un texte via `POST /detect`. */\n async detect(text: string): Promise<DetectedLanguage[]> {\n if (!text) throw new Error(\"detect: 'text' is required.\");\n return this.request<DetectedLanguage[]>(\"POST\", \"/detect\", { q: text });\n }\n\n /** Liste les langues supportées via `GET /languages`. */\n async languages(): Promise<Language[]> {\n return this.request<Language[]>(\"GET\", \"/languages\");\n }\n\n /** État du service via `GET /health` (ne nécessite pas de clé API). */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>(\"GET\", \"/health\", undefined, { auth: false });\n }\n\n private async request<T>(\n method: HttpMethod,\n path: string,\n body?: Record<string, unknown>,\n opts?: { auth?: boolean },\n ): Promise<T> {\n const useAuth = opts?.auth !== false;\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = { Accept: \"application/json\", ...this.extraHeaders };\n if (useAuth) headers[\"X-API-Key\"] = this.apiKey;\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this.fetchImpl(url, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n const e = err as { name?: string; message?: string };\n const msg =\n e?.name === \"AbortError\"\n ? `Request timed out after ${this.timeoutMs} ms (${path}).`\n : `Network error to ${url}: ${e?.message ?? String(err)}`;\n throw new ETranslateError(msg, 0);\n } finally {\n clearTimeout(timer);\n }\n\n const raw = await resp.text();\n let data: unknown = undefined;\n if (raw) {\n try {\n data = JSON.parse(raw);\n } catch {\n data = raw;\n }\n }\n\n if (!resp.ok) {\n const message =\n data && typeof data === \"object\" && \"error\" in data\n ? String((data as { error: unknown }).error)\n : `HTTP ${resp.status} sur ${path}`;\n throw new ETranslateError(message, resp.status, data);\n }\n\n return data as T;\n }\n}\n\n/** Fabrique pratique équivalente à `new ETranslateClient(options)`. */\nexport function createClient(options: ETranslateOptions): ETranslateClient {\n return new ETranslateClient(options);\n}\n\nexport default ETranslateClient;\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ var ETranslateError = class _ETranslateError extends Error {
|
|
|
11
11
|
var ETranslateClient = class {
|
|
12
12
|
constructor(options) {
|
|
13
13
|
if (!options || typeof options.baseUrl !== "string" || options.baseUrl.length === 0) {
|
|
14
|
-
throw new Error("ETranslate:
|
|
14
|
+
throw new Error("ETranslate: the 'baseUrl' option is required.");
|
|
15
15
|
}
|
|
16
16
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
17
17
|
this.apiKey = options.apiKey ?? "";
|
|
@@ -19,8 +19,8 @@ var ETranslateClient = class {
|
|
|
19
19
|
this.extraHeaders = options.headers ?? {};
|
|
20
20
|
const f = options.fetch ?? globalThis.fetch;
|
|
21
21
|
if (typeof f !== "function") {
|
|
22
|
-
throw new
|
|
23
|
-
"ETranslate:
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
"ETranslate: no `fetch` implementation available. Use Node >= 18 or pass `options.fetch`."
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
this.fetchImpl = f;
|
|
@@ -30,11 +30,11 @@ var ETranslateClient = class {
|
|
|
30
30
|
* Envoie `lang_src` / `lang_dest` / `message` à `POST /translate`.
|
|
31
31
|
*/
|
|
32
32
|
async translate(params) {
|
|
33
|
-
if (
|
|
34
|
-
throw new Error("translate: 'message'
|
|
33
|
+
if (params?.message == null) {
|
|
34
|
+
throw new Error("translate: 'message' is required.");
|
|
35
35
|
}
|
|
36
|
-
if (!params.from) throw new Error("translate: 'from'
|
|
37
|
-
if (!params.to) throw new Error("translate: 'to'
|
|
36
|
+
if (!params.from) throw new Error("translate: 'from' is required (e.g. 'fr' or 'auto').");
|
|
37
|
+
if (!params.to) throw new Error("translate: 'to' is required (e.g. 'en').");
|
|
38
38
|
return this.request("POST", "/translate", {
|
|
39
39
|
lang_src: params.from,
|
|
40
40
|
lang_dest: params.to,
|
|
@@ -44,7 +44,7 @@ var ETranslateClient = class {
|
|
|
44
44
|
}
|
|
45
45
|
/** Détecte la langue d'un texte via `POST /detect`. */
|
|
46
46
|
async detect(text) {
|
|
47
|
-
if (!text) throw new Error("detect: 'text'
|
|
47
|
+
if (!text) throw new Error("detect: 'text' is required.");
|
|
48
48
|
return this.request("POST", "/detect", { q: text });
|
|
49
49
|
}
|
|
50
50
|
/** Liste les langues supportées via `GET /languages`. */
|
|
@@ -68,12 +68,12 @@ var ETranslateClient = class {
|
|
|
68
68
|
resp = await this.fetchImpl(url, {
|
|
69
69
|
method,
|
|
70
70
|
headers,
|
|
71
|
-
body: body
|
|
71
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
72
72
|
signal: controller.signal
|
|
73
73
|
});
|
|
74
74
|
} catch (err) {
|
|
75
75
|
const e = err;
|
|
76
|
-
const msg = e?.name === "AbortError" ? `
|
|
76
|
+
const msg = e?.name === "AbortError" ? `Request timed out after ${this.timeoutMs} ms (${path}).` : `Network error to ${url}: ${e?.message ?? String(err)}`;
|
|
77
77
|
throw new ETranslateError(msg, 0);
|
|
78
78
|
} finally {
|
|
79
79
|
clearTimeout(timer);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * e-translate SDK — client TypeScript/JavaScript pour l'API e-translate.\n *\n * Fonctionne dans Node 18+ (fetch global) et dans le navigateur.\n *\n * @example\n * ```ts\n * import { ETranslateClient } from \"e-translate-sdk\";\n *\n * const client = new ETranslateClient({\n * baseUrl: \"https://translation.e-cosplay.fr\",\n * apiKey: \"etk_xxxxxxxx\",\n * });\n *\n * const res = await client.translate({ message: \"Bonjour\", from: \"fr\", to: \"en\" });\n * console.log(res.translatedText); // \"Hello\"\n * ```\n */\n\n/** Format du texte à traduire. */\nexport type TranslateFormat = \"text\" | \"html\";\n\n/** Méthode HTTP interne. */\ntype HttpMethod = \"GET\" | \"POST\";\n\n/** Options de construction du client. */\nexport interface ETranslateOptions {\n /** URL de base du serveur, ex. \"https://translation.e-cosplay.fr\". */\n baseUrl: string;\n /** Clé d'API (`etk_...`). Requise pour /translate, /detect, /languages. */\n apiKey: string;\n /** Timeout par requête en millisecondes (défaut : 20000). */\n timeoutMs?: number;\n /** Implémentation `fetch` personnalisée (Node < 18, tests). Défaut : `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** En-têtes HTTP supplémentaires envoyés à chaque requête. */\n headers?: Record<string, string>;\n}\n\n/** Paramètres de traduction. */\nexport interface TranslateParams {\n /** Texte (ou tableau de textes) à traduire. */\n message: string | string[];\n /** Langue source (code ISO, ex. \"fr\") ou \"auto\" pour la détection automatique. */\n from: string;\n /** Langue cible (code ISO, ex. \"en\"). */\n to: string;\n /** Format du texte (défaut : \"text\"). */\n format?: TranslateFormat;\n}\n\n/** Langue détectée renvoyée par /detect ou en mode \"auto\". */\nexport interface DetectedLanguage {\n /** Indice de confiance (0–100). */\n confidence: number;\n /** Code ISO de la langue détectée. */\n language: string;\n}\n\n/** Résultat d'une traduction. */\nexport interface TranslateResult {\n /** Texte traduit (string si `message` était une string, sinon string[]). */\n translatedText: string | string[];\n /** `true` si la réponse provient du cache Redis. */\n cached: boolean;\n /** Présent uniquement si `from` valait \"auto\". */\n detectedLanguage?: DetectedLanguage;\n}\n\n/** Une langue supportée par le serveur. */\nexport interface Language {\n code: string;\n name: string;\n targets?: string[];\n}\n\n/** Réponse de /health (aucune clé requise). */\nexport interface HealthResult {\n status: string;\n redis: boolean;\n /** Disponibilité du moteur de traduction en amont. */\n engine: boolean;\n time: string;\n}\n\n/**\n * Erreur levée pour toute réponse non-2xx ou tout échec réseau.\n * `status` vaut 0 en cas d'échec réseau / timeout.\n */\nexport class ETranslateError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ETranslateError\";\n this.status = status;\n this.body = body;\n // Restaure la chaîne de prototype (TS ciblant ES5/ES2015).\n Object.setPrototypeOf(this, ETranslateError.prototype);\n }\n}\n\n/** Client de l'API e-translate. */\nexport class ETranslateClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly extraHeaders: Record<string, string>;\n\n constructor(options: ETranslateOptions) {\n if (!options || typeof options.baseUrl !== \"string\" || options.baseUrl.length === 0) {\n throw new Error(\"ETranslate:
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * e-translate SDK — client TypeScript/JavaScript pour l'API e-translate.\n *\n * Fonctionne dans Node 18+ (fetch global) et dans le navigateur.\n *\n * @example\n * ```ts\n * import { ETranslateClient } from \"e-translate-sdk\";\n *\n * const client = new ETranslateClient({\n * baseUrl: \"https://translation.e-cosplay.fr\",\n * apiKey: \"etk_xxxxxxxx\",\n * });\n *\n * const res = await client.translate({ message: \"Bonjour\", from: \"fr\", to: \"en\" });\n * console.log(res.translatedText); // \"Hello\"\n * ```\n */\n\n/** Format du texte à traduire. */\nexport type TranslateFormat = \"text\" | \"html\";\n\n/** Méthode HTTP interne. */\ntype HttpMethod = \"GET\" | \"POST\";\n\n/** Options de construction du client. */\nexport interface ETranslateOptions {\n /** URL de base du serveur, ex. \"https://translation.e-cosplay.fr\". */\n baseUrl: string;\n /** Clé d'API (`etk_...`). Requise pour /translate, /detect, /languages. */\n apiKey: string;\n /** Timeout par requête en millisecondes (défaut : 20000). */\n timeoutMs?: number;\n /** Implémentation `fetch` personnalisée (Node < 18, tests). Défaut : `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** En-têtes HTTP supplémentaires envoyés à chaque requête. */\n headers?: Record<string, string>;\n}\n\n/** Paramètres de traduction. */\nexport interface TranslateParams {\n /** Texte (ou tableau de textes) à traduire. */\n message: string | string[];\n /** Langue source (code ISO, ex. \"fr\") ou \"auto\" pour la détection automatique. */\n from: string;\n /** Langue cible (code ISO, ex. \"en\"). */\n to: string;\n /** Format du texte (défaut : \"text\"). */\n format?: TranslateFormat;\n}\n\n/** Langue détectée renvoyée par /detect ou en mode \"auto\". */\nexport interface DetectedLanguage {\n /** Indice de confiance (0–100). */\n confidence: number;\n /** Code ISO de la langue détectée. */\n language: string;\n}\n\n/** Résultat d'une traduction. */\nexport interface TranslateResult {\n /** Texte traduit (string si `message` était une string, sinon string[]). */\n translatedText: string | string[];\n /** `true` si la réponse provient du cache Redis. */\n cached: boolean;\n /** Présent uniquement si `from` valait \"auto\". */\n detectedLanguage?: DetectedLanguage;\n}\n\n/** Une langue supportée par le serveur. */\nexport interface Language {\n code: string;\n name: string;\n targets?: string[];\n}\n\n/** Réponse de /health (aucune clé requise). */\nexport interface HealthResult {\n status: string;\n redis: boolean;\n /** Disponibilité du moteur de traduction en amont. */\n engine: boolean;\n time: string;\n}\n\n/**\n * Erreur levée pour toute réponse non-2xx ou tout échec réseau.\n * `status` vaut 0 en cas d'échec réseau / timeout.\n */\nexport class ETranslateError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ETranslateError\";\n this.status = status;\n this.body = body;\n // Restaure la chaîne de prototype (TS ciblant ES5/ES2015).\n Object.setPrototypeOf(this, ETranslateError.prototype);\n }\n}\n\n/** Client de l'API e-translate. */\nexport class ETranslateClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly extraHeaders: Record<string, string>;\n\n constructor(options: ETranslateOptions) {\n if (!options || typeof options.baseUrl !== \"string\" || options.baseUrl.length === 0) {\n throw new Error(\"ETranslate: the 'baseUrl' option is required.\");\n }\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = options.apiKey ?? \"\";\n this.timeoutMs = options.timeoutMs ?? 20000;\n this.extraHeaders = options.headers ?? {};\n\n const f = options.fetch ?? (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof f !== \"function\") {\n throw new TypeError(\n \"ETranslate: no `fetch` implementation available. \" +\n \"Use Node >= 18 or pass `options.fetch`.\",\n );\n }\n this.fetchImpl = f;\n }\n\n /**\n * Traduit un texte (ou un tableau de textes).\n * Envoie `lang_src` / `lang_dest` / `message` à `POST /translate`.\n */\n async translate(params: TranslateParams): Promise<TranslateResult> {\n if (params?.message == null) {\n throw new Error(\"translate: 'message' is required.\");\n }\n if (!params.from) throw new Error(\"translate: 'from' is required (e.g. 'fr' or 'auto').\");\n if (!params.to) throw new Error(\"translate: 'to' is required (e.g. 'en').\");\n\n return this.request<TranslateResult>(\"POST\", \"/translate\", {\n lang_src: params.from,\n lang_dest: params.to,\n message: params.message,\n format: params.format ?? \"text\",\n });\n }\n\n /** Détecte la langue d'un texte via `POST /detect`. */\n async detect(text: string): Promise<DetectedLanguage[]> {\n if (!text) throw new Error(\"detect: 'text' is required.\");\n return this.request<DetectedLanguage[]>(\"POST\", \"/detect\", { q: text });\n }\n\n /** Liste les langues supportées via `GET /languages`. */\n async languages(): Promise<Language[]> {\n return this.request<Language[]>(\"GET\", \"/languages\");\n }\n\n /** État du service via `GET /health` (ne nécessite pas de clé API). */\n async health(): Promise<HealthResult> {\n return this.request<HealthResult>(\"GET\", \"/health\", undefined, { auth: false });\n }\n\n private async request<T>(\n method: HttpMethod,\n path: string,\n body?: Record<string, unknown>,\n opts?: { auth?: boolean },\n ): Promise<T> {\n const useAuth = opts?.auth !== false;\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = { Accept: \"application/json\", ...this.extraHeaders };\n if (useAuth) headers[\"X-API-Key\"] = this.apiKey;\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this.fetchImpl(url, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n const e = err as { name?: string; message?: string };\n const msg =\n e?.name === \"AbortError\"\n ? `Request timed out after ${this.timeoutMs} ms (${path}).`\n : `Network error to ${url}: ${e?.message ?? String(err)}`;\n throw new ETranslateError(msg, 0);\n } finally {\n clearTimeout(timer);\n }\n\n const raw = await resp.text();\n let data: unknown = undefined;\n if (raw) {\n try {\n data = JSON.parse(raw);\n } catch {\n data = raw;\n }\n }\n\n if (!resp.ok) {\n const message =\n data && typeof data === \"object\" && \"error\" in data\n ? String((data as { error: unknown }).error)\n : `HTTP ${resp.status} sur ${path}`;\n throw new ETranslateError(message, resp.status, data);\n }\n\n return data as T;\n }\n}\n\n/** Fabrique pratique équivalente à `new ETranslateClient(options)`. */\nexport function createClient(options: ETranslateOptions): ETranslateClient {\n return new ETranslateClient(options);\n}\n\nexport default ETranslateClient;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecosplay/e-translate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Client SDK (TypeScript) pour l'API e-translate (translation.e-cosplay.fr).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -25,12 +25,13 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup",
|
|
27
27
|
"prepare": "tsup",
|
|
28
|
-
"typecheck": "tsc --noEmit"
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:coverage": "vitest run --coverage"
|
|
29
31
|
},
|
|
30
32
|
"keywords": [
|
|
31
33
|
"translate",
|
|
32
34
|
"translation",
|
|
33
|
-
"libretranslate",
|
|
34
35
|
"e-translate",
|
|
35
36
|
"sdk",
|
|
36
37
|
"i18n"
|
|
@@ -45,7 +46,9 @@
|
|
|
45
46
|
"url": "ssh://git@code.e-cosplay.fr:222/shoko/e-translate-sdk-js.git"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
49
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
48
50
|
"tsup": "^8.3.0",
|
|
49
|
-
"typescript": "^5.6.0"
|
|
51
|
+
"typescript": "^5.6.0",
|
|
52
|
+
"vitest": "^2.1.8"
|
|
50
53
|
}
|
|
51
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -111,7 +111,7 @@ export class ETranslateClient {
|
|
|
111
111
|
|
|
112
112
|
constructor(options: ETranslateOptions) {
|
|
113
113
|
if (!options || typeof options.baseUrl !== "string" || options.baseUrl.length === 0) {
|
|
114
|
-
throw new Error("ETranslate:
|
|
114
|
+
throw new Error("ETranslate: the 'baseUrl' option is required.");
|
|
115
115
|
}
|
|
116
116
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
117
117
|
this.apiKey = options.apiKey ?? "";
|
|
@@ -120,9 +120,9 @@ export class ETranslateClient {
|
|
|
120
120
|
|
|
121
121
|
const f = options.fetch ?? (globalThis as { fetch?: typeof fetch }).fetch;
|
|
122
122
|
if (typeof f !== "function") {
|
|
123
|
-
throw new
|
|
124
|
-
"ETranslate:
|
|
125
|
-
"
|
|
123
|
+
throw new TypeError(
|
|
124
|
+
"ETranslate: no `fetch` implementation available. " +
|
|
125
|
+
"Use Node >= 18 or pass `options.fetch`.",
|
|
126
126
|
);
|
|
127
127
|
}
|
|
128
128
|
this.fetchImpl = f;
|
|
@@ -133,11 +133,11 @@ export class ETranslateClient {
|
|
|
133
133
|
* Envoie `lang_src` / `lang_dest` / `message` à `POST /translate`.
|
|
134
134
|
*/
|
|
135
135
|
async translate(params: TranslateParams): Promise<TranslateResult> {
|
|
136
|
-
if (
|
|
137
|
-
throw new Error("translate: 'message'
|
|
136
|
+
if (params?.message == null) {
|
|
137
|
+
throw new Error("translate: 'message' is required.");
|
|
138
138
|
}
|
|
139
|
-
if (!params.from) throw new Error("translate: 'from'
|
|
140
|
-
if (!params.to) throw new Error("translate: 'to'
|
|
139
|
+
if (!params.from) throw new Error("translate: 'from' is required (e.g. 'fr' or 'auto').");
|
|
140
|
+
if (!params.to) throw new Error("translate: 'to' is required (e.g. 'en').");
|
|
141
141
|
|
|
142
142
|
return this.request<TranslateResult>("POST", "/translate", {
|
|
143
143
|
lang_src: params.from,
|
|
@@ -149,7 +149,7 @@ export class ETranslateClient {
|
|
|
149
149
|
|
|
150
150
|
/** Détecte la langue d'un texte via `POST /detect`. */
|
|
151
151
|
async detect(text: string): Promise<DetectedLanguage[]> {
|
|
152
|
-
if (!text) throw new Error("detect: 'text'
|
|
152
|
+
if (!text) throw new Error("detect: 'text' is required.");
|
|
153
153
|
return this.request<DetectedLanguage[]>("POST", "/detect", { q: text });
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -184,15 +184,15 @@ export class ETranslateClient {
|
|
|
184
184
|
resp = await this.fetchImpl(url, {
|
|
185
185
|
method,
|
|
186
186
|
headers,
|
|
187
|
-
body: body
|
|
187
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
188
188
|
signal: controller.signal,
|
|
189
189
|
});
|
|
190
190
|
} catch (err) {
|
|
191
191
|
const e = err as { name?: string; message?: string };
|
|
192
192
|
const msg =
|
|
193
193
|
e?.name === "AbortError"
|
|
194
|
-
? `
|
|
195
|
-
:
|
|
194
|
+
? `Request timed out after ${this.timeoutMs} ms (${path}).`
|
|
195
|
+
: `Network error to ${url}: ${e?.message ?? String(err)}`;
|
|
196
196
|
throw new ETranslateError(msg, 0);
|
|
197
197
|
} finally {
|
|
198
198
|
clearTimeout(timer);
|