@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 CHANGED
@@ -9,13 +9,13 @@
9
9
  [![Reliability Rating](https://sn.e-cosplay.fr/api/project_badges/measure?project=e-translate-sdk-js&metric=software_quality_reliability_rating&token=sqb_af5f3b3f8adae526165e73f0ed40f1fc9525e9b1)](https://sn.e-cosplay.fr/dashboard?id=e-translate-sdk-js)
10
10
  [![Security Rating](https://sn.e-cosplay.fr/api/project_badges/measure?project=e-translate-sdk-js&metric=software_quality_security_rating&token=sqb_af5f3b3f8adae526165e73f0ed40f1fc9525e9b1)](https://sn.e-cosplay.fr/dashboard?id=e-translate-sdk-js)
11
11
 
12
- Client **TypeScript / JavaScript** pour l'API [e-translate](https://translation.e-cosplay.fr)
13
- (traduction via une API authentifiée par clé, avec mise en cache).
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
- - ✅ Fonctionne dans **Node.js ≥ 18** (fetch natif) et dans le **navigateur**
16
- - ✅ **TypeScript** (types fournis) — sorties **ESM + CJS**
17
- - ✅ Zéro dépendance runtime
18
- - ✅ Disponible sur **npm** (`@ecosplay/e-translate`) et installable **par git**
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
- Avec pnpm ou yarn :
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
- **Épingler une version** (conseillé en production) :
35
+ **Pin a version** (recommended in production):
36
36
 
37
37
  ```bash
38
- npm install @ecosplay/e-translate@1.0.0
38
+ npm install @ecosplay/e-translate@1.1.0
39
39
  ```
40
40
 
41
- > Publié automatiquement sur npm par la CI à chaque tag `vX.Y.Z` (voir
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
- ## 🚀 Utilisation
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 puis true au 2e appel identique
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
- ### Détection automatique, tableaux, HTML
76
+ ### Auto-detection, arrays, HTML
77
77
 
78
78
  ```ts
79
- // from: "auto" -> la langue source est détectée
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
- // Traduire plusieurs textes d'un coup
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
- // Conserver le balisage HTML
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 | Défaut | Description |
98
- | ----------- | ------------------------ | ------------------ | -------------------------------------------- |
99
- | `baseUrl` | `string` | — | URL du serveur (requis) |
100
- | `apiKey` | `string` | — | Clé `etk_…` (requise sauf pour `health()`) |
101
- | `timeoutMs` | `number` | `20000` | Timeout par requête |
102
- | `fetch` | `typeof fetch` | `globalThis.fetch` | Implémentation fetch (Node < 18, tests) |
103
- | `headers` | `Record<string,string>` | `{}` | En-têtes additionnels |
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
- ### Méthodes
105
+ ### Methods
106
106
 
107
- | Méthode | Endpoint | Retour |
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>` |
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` : `{ message: string | string[]; from: string; to: string; format?: "text" | "html" }`.
115
- (`from` accepte `"auto"`. Les alias serveur `source`/`target`/`q` sont gérés côté serveur.)
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
- ### Gestion des erreurs
117
+ ### Error handling
118
118
 
119
- Toute réponse non-2xx (ou un échec réseau) lève une **`ETranslateError`** :
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 (clé invalide), 429 (rate limit), 502 (upstream), 0 (réseau/timeout)
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); // corps JSON renvoyé par le serveur, si présent
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
- ## 🛠️ Build depuis les sources
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 # exécute aussi le build via "prepare"
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 # vérification de types sans émettre
159
+ npm run typecheck # type-check without emitting
145
160
  ```
146
161
 
147
- ## Licence
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: l'option 'baseUrl' est requise.");
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 Error(
50
- "ETranslate: aucune impl\xE9mentation `fetch` disponible. Utilisez Node >= 18 ou passez `options.fetch`."
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 (!params || params.message == null) {
61
- throw new Error("translate: 'message' est requis.");
60
+ if (params?.message == null) {
61
+ throw new Error("translate: 'message' is required.");
62
62
  }
63
- if (!params.from) throw new Error("translate: 'from' est requis (ex. 'fr' ou 'auto').");
64
- if (!params.to) throw new Error("translate: 'to' est requis (ex. 'en').");
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' est requis.");
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 !== void 0 ? JSON.stringify(body) : void 0,
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" ? `Requ\xEAte expir\xE9e apr\xE8s ${this.timeoutMs} ms (${path}).` : `\xC9chec r\xE9seau vers ${url}: ${e?.message ?? String(err)}`;
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);
@@ -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: l'option 'baseUrl' est requise.");
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 Error(
23
- "ETranslate: aucune impl\xE9mentation `fetch` disponible. Utilisez Node >= 18 ou passez `options.fetch`."
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 (!params || params.message == null) {
34
- throw new Error("translate: 'message' est requis.");
33
+ if (params?.message == null) {
34
+ throw new Error("translate: 'message' is required.");
35
35
  }
36
- if (!params.from) throw new Error("translate: 'from' est requis (ex. 'fr' ou 'auto').");
37
- if (!params.to) throw new Error("translate: 'to' est requis (ex. 'en').");
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' est requis.");
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 !== void 0 ? JSON.stringify(body) : void 0,
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" ? `Requ\xEAte expir\xE9e apr\xE8s ${this.timeoutMs} ms (${path}).` : `\xC9chec r\xE9seau vers ${url}: ${e?.message ?? String(err)}`;
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: 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecosplay/e-translate",
3
- "version": "1.0.0",
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: l'option 'baseUrl' est requise.");
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 Error(
124
- "ETranslate: aucune implémentation `fetch` disponible. " +
125
- "Utilisez Node >= 18 ou passez `options.fetch`.",
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 (!params || params.message == null) {
137
- throw new Error("translate: 'message' est requis.");
136
+ if (params?.message == null) {
137
+ throw new Error("translate: 'message' is required.");
138
138
  }
139
- if (!params.from) throw new Error("translate: 'from' est requis (ex. 'fr' ou 'auto').");
140
- if (!params.to) throw new Error("translate: 'to' est requis (ex. 'en').");
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' est requis.");
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 !== undefined ? JSON.stringify(body) : undefined,
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
- ? `Requête expirée après ${this.timeoutMs} ms (${path}).`
195
- : `Échec réseau vers ${url}: ${e?.message ?? String(err)}`;
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);