@cutticat/libretranslate 1.0.3

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.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maksim Victorovich Fomin
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,81 @@
1
+ # @cutticat/libretranslate
2
+
3
+ Library for working with the LibreTranslate API.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [API](#api)
11
+ - [Documentation](#documentation)
12
+ - [Requirements](#requirements)
13
+
14
+ ## Overview
15
+
16
+ The library provides the `translate` function for translating text via the LibreTranslate API. Supports language auto-detection, alternative translations, custom baseUrl and API key. Uses Zod for response validation and types from `@cutticat/types`.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm i @cutticat/libretranslate
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { translate } from "@cutticat/libretranslate"
28
+
29
+ const result = await translate({
30
+ text: "Hello, world!",
31
+ source: "en",
32
+ target: "ru",
33
+ })
34
+ console.log(result.translatedText) // "Привет, мир!"
35
+ ```
36
+
37
+ With language auto-detection:
38
+
39
+ ```typescript
40
+ const result = await translate({
41
+ text: "Bonjour",
42
+ source: "auto",
43
+ target: "en",
44
+ })
45
+ console.log(result.translatedText) // "Hello"
46
+ console.log(result.detectedLanguage) // { code: "fr", confidence: 0.95 }
47
+ ```
48
+
49
+ ## API
50
+
51
+ ### Functions
52
+
53
+ - `translate(options)` — translates text via the LibreTranslate API; returns `Promise<TranslateResponse>`; throws `Error` on request failure
54
+
55
+ ### Types
56
+
57
+ - `TranslationOptions` — translation parameters (`text`, `source`, `target`, `format?`, `alternatives?`, `baseUrl?`, `apiKey?`, `fetch?`)
58
+ - `TranslationFormat` — `"text"` | `"html"`
59
+ - `TranslateResponse` — response (`translatedText`, `detectedLanguage?`, `alternatives?`)
60
+ - `DetectedLanguage` — detected language info (`code`, `confidence`)
61
+
62
+ ### Constants
63
+
64
+ - `defaultBaseUrl` — default URL (`https://libretranslate.com`)
65
+
66
+ ## Documentation
67
+
68
+ Detailed documentation:
69
+
70
+ - [Project structure](documents/STRUCTURE.md)
71
+ - [Scripts](documents/SCRIPTS.md)
72
+ - [Style guide](documents/STYLE_GUIDE.md)
73
+ - [Contributing](documents/CONTRIBUTING.md)
74
+
75
+ Full API documentation is available in JSDoc comments. Use IDE autocomplete to browse it.
76
+
77
+ ## Requirements
78
+
79
+ - Node.js >= 22.0.0
80
+ - pnpm >= 10.17.0
81
+ - TypeScript >= 5.9.0
@@ -0,0 +1,133 @@
1
+ import { z } from "zod";
2
+ import { DeepReadonly, FullyUndefinable } from "@cutticat/types";
3
+
4
+ //#region lib/schemas/DetectedLanguage.d.ts
5
+ /** Detected language info schema. */
6
+ declare const DetectedLanguageSchema: z.ZodObject<{
7
+ code: z.ZodString;
8
+ confidence: z.ZodNumber;
9
+ }, z.core.$strip>;
10
+ /** Detected language info (when using auto-detection). */
11
+ type DetectedLanguage = z.infer<typeof DetectedLanguageSchema>;
12
+ //#endregion
13
+ //#region lib/schemas/TranslateResponse.d.ts
14
+ /** Translation response schema. */
15
+ declare const TranslateResponseSchema: z.ZodObject<{
16
+ translatedText: z.ZodString;
17
+ detectedLanguage: z.ZodOptional<z.ZodObject<{
18
+ code: z.ZodString;
19
+ confidence: z.ZodNumber;
20
+ }, z.core.$strip>>;
21
+ alternatives: z.ZodOptional<z.ZodArray<z.ZodString>>;
22
+ }, z.core.$strip>;
23
+ /** Translation response from the LibreTranslate API. */
24
+ type TranslateResponse = z.infer<typeof TranslateResponseSchema>;
25
+ //#endregion
26
+ //#region lib/types/TranslationFormat.d.ts
27
+ /** Text format: "text" or "html" for markup. */
28
+ type TranslationFormat = "text" | "html";
29
+ //#endregion
30
+ //#region lib/types/TranslationOptions.d.ts
31
+ /** Translation request parameters. */
32
+ type TranslationOptions = {
33
+ /** Text to translate. */
34
+ text: string;
35
+ /** Source language code (e.g. "en", "ru") or "auto" for auto-detection. */
36
+ source: string;
37
+ /** Target language code (e.g. "en", "ru"). */
38
+ target: string;
39
+ } & FullyUndefinable<{
40
+ /** Text format: "text" (default) or "html" for markup. */
41
+ format: TranslationFormat;
42
+ /** Number of alternative translations. */
43
+ alternatives: number;
44
+ /** Base URL of the LibreTranslate API instance. */
45
+ baseUrl: string;
46
+ /** API key (required for libretranslate.com; optional for self-hosted). */
47
+ apiKey: string;
48
+ /** fetch function for HTTP requests (defaults to globalThis.fetch; pass a mock for tests). */
49
+ fetch: typeof globalThis.fetch;
50
+ }>;
51
+ //#endregion
52
+ //#region lib/constants.d.ts
53
+ /** Default URL for the LibreTranslate API. */
54
+ declare const defaultBaseUrl = "https://libretranslate.com";
55
+ //#endregion
56
+ //#region lib/functions.d.ts
57
+ /**
58
+ * Translates text via the LibreTranslate API.
59
+ *
60
+ * @param options — translation parameters
61
+ * @returns Promise with translation response
62
+ * @throws {Error} On request failure (HTTP status included in message)
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const result = await translate({
67
+ * text: "Hello, world!",
68
+ * source: "en",
69
+ * target: "ru"
70
+ * })
71
+ * console.log(result.translatedText) // "Привет, мир!"
72
+ * ```
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // With language auto-detection
77
+ * const result = await translate({
78
+ * text: "Bonjour",
79
+ * source: "auto",
80
+ * target: "en"
81
+ * })
82
+ * console.log(result.translatedText) // "Hello"
83
+ * console.log(result.detectedLanguage) // { code: "fr", confidence: 0.95 }
84
+ * ```
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // With custom API and key
89
+ * const result = await translate({
90
+ * text: "Hello",
91
+ * source: "en",
92
+ * target: "es",
93
+ * baseUrl: "https://translate.example.com",
94
+ * apiKey: "your-api-key"
95
+ * })
96
+ * ```
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * // With custom fetch (e.g. for tests or proxy)
101
+ * const result = await translate({
102
+ * text: "Hello",
103
+ * source: "en",
104
+ * target: "ru",
105
+ * fetch: myCustomFetch
106
+ * })
107
+ * ```
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // With alternative translations
112
+ * const result = await translate({
113
+ * text: "Hello",
114
+ * source: "en",
115
+ * target: "ru",
116
+ * alternatives: 2
117
+ * })
118
+ * console.log(result.alternatives) // ["Здравствуй", "Приветствую"]
119
+ * ```
120
+ */
121
+ declare function translate({
122
+ text,
123
+ source,
124
+ target,
125
+ format,
126
+ alternatives,
127
+ apiKey,
128
+ baseUrl,
129
+ fetch
130
+ }: DeepReadonly<TranslationOptions>): Promise<TranslateResponse>;
131
+ //#endregion
132
+ export { DetectedLanguage, DetectedLanguageSchema, TranslateResponse, TranslateResponseSchema, TranslationFormat, TranslationOptions, defaultBaseUrl, translate };
133
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../lib/schemas/DetectedLanguage.ts","../lib/schemas/TranslateResponse.ts","../lib/types/TranslationFormat.ts","../lib/types/TranslationOptions.ts","../lib/constants.ts","../lib/functions.ts"],"sourcesContent":[],"mappings":";;;;;cAGa,wBAAsB,CAAA,CAAA;;EAAtB,UAAA,aAAA;;;KAMD,gBAAA,GAAmB,CAAA,CAAE,aAAa;;;;cCLjC,yBAAuB,CAAA,CAAA;;EDDvB,gBAAA,eAGX,YAAA,CAAA;;;;EAHiC,YAAA,eAAA,WAAA,YAAA,CAAA,CAAA;CAAA,eAAA,CAAA;AAMnC;KCEY,iBAAA,GAAoB,CAAA,CAAE,aAAa;;;;KCVnC,iBAAA;;;;KCGA,kBAAA;EHDC;;;;EAAsB;EAAA,MAAA,EAAA,MAAA;AAMnC,CAAA,GGEI,gBHFQ,CAAA;;UGIF;;EFTG,YAAA,EAAA,MAAA;;;;;;gBEiBG,UAAA,CAAW;;;;;cCpBd,cAAA;;;;AJEb;;;;;;AAMA;;;;ACLA;;;;;;;;;;;;;AAOA;;;;ACVA;;;;ACGA;;;;;;;;ACHA;;;;ACoEA;;;;;;;;;;;;;;;;;;;;iBAAsB,SAAA;;;;;;;;;GASnB,aAAa,sBAAsB,QAAQ"}
package/dist/index.js ADDED
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+
3
+ //#region lib/schemas/DetectedLanguage.ts
4
+ /** Detected language info schema. */
5
+ const DetectedLanguageSchema = z.object({
6
+ code: z.string(),
7
+ confidence: z.number()
8
+ });
9
+
10
+ //#endregion
11
+ //#region lib/schemas/TranslateResponse.ts
12
+ /** Translation response schema. */
13
+ const TranslateResponseSchema = z.object({
14
+ translatedText: z.string(),
15
+ detectedLanguage: DetectedLanguageSchema.optional(),
16
+ alternatives: z.array(z.string()).optional()
17
+ });
18
+
19
+ //#endregion
20
+ //#region lib/constants.ts
21
+ /** Default URL for the LibreTranslate API. */
22
+ const defaultBaseUrl = "https://libretranslate.com";
23
+
24
+ //#endregion
25
+ //#region lib/functions.ts
26
+ /**
27
+ * Translates text via the LibreTranslate API.
28
+ *
29
+ * @param options — translation parameters
30
+ * @returns Promise with translation response
31
+ * @throws {Error} On request failure (HTTP status included in message)
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const result = await translate({
36
+ * text: "Hello, world!",
37
+ * source: "en",
38
+ * target: "ru"
39
+ * })
40
+ * console.log(result.translatedText) // "Привет, мир!"
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * // With language auto-detection
46
+ * const result = await translate({
47
+ * text: "Bonjour",
48
+ * source: "auto",
49
+ * target: "en"
50
+ * })
51
+ * console.log(result.translatedText) // "Hello"
52
+ * console.log(result.detectedLanguage) // { code: "fr", confidence: 0.95 }
53
+ * ```
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * // With custom API and key
58
+ * const result = await translate({
59
+ * text: "Hello",
60
+ * source: "en",
61
+ * target: "es",
62
+ * baseUrl: "https://translate.example.com",
63
+ * apiKey: "your-api-key"
64
+ * })
65
+ * ```
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // With custom fetch (e.g. for tests or proxy)
70
+ * const result = await translate({
71
+ * text: "Hello",
72
+ * source: "en",
73
+ * target: "ru",
74
+ * fetch: myCustomFetch
75
+ * })
76
+ * ```
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // With alternative translations
81
+ * const result = await translate({
82
+ * text: "Hello",
83
+ * source: "en",
84
+ * target: "ru",
85
+ * alternatives: 2
86
+ * })
87
+ * console.log(result.alternatives) // ["Здравствуй", "Приветствую"]
88
+ * ```
89
+ */
90
+ async function translate({ text, source, target, format = "text", alternatives = 0, apiKey, baseUrl = defaultBaseUrl, fetch = globalThis.fetch }) {
91
+ if (alternatives < 0) throw new Error("Number of alternatives must be zero or positive");
92
+ if (!Number.isInteger(alternatives)) throw new Error("Number of alternatives must be an integer");
93
+ const body = {
94
+ q: text,
95
+ source,
96
+ target
97
+ };
98
+ if (format != null) body.format = format;
99
+ if (alternatives > 0) body.alternatives = alternatives;
100
+ if (apiKey != null) body.api_key = apiKey;
101
+ const response = await fetch(new URL("/translate", baseUrl), {
102
+ method: "POST",
103
+ headers: { "Content-Type": "application/json" },
104
+ body: JSON.stringify(body)
105
+ });
106
+ if (!response.ok) throw new Error(`Failed to translate text: ${response.status} ${response.statusText}`);
107
+ const json = await response.json();
108
+ return TranslateResponseSchema.parse(json);
109
+ }
110
+
111
+ //#endregion
112
+ export { DetectedLanguageSchema, TranslateResponseSchema, defaultBaseUrl, translate };
113
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../lib/schemas/DetectedLanguage.ts","../lib/schemas/TranslateResponse.ts","../lib/constants.ts","../lib/functions.ts"],"sourcesContent":["import { z } from \"zod\"\n\n/** Detected language info schema. */\nexport const DetectedLanguageSchema = z.object({\n code: z.string(),\n confidence: z.number(),\n})\n\n/** Detected language info (when using auto-detection). */\nexport type DetectedLanguage = z.infer<typeof DetectedLanguageSchema>\n","import { z } from \"zod\"\nimport { DetectedLanguageSchema } from \"./DetectedLanguage.js\"\n\n/** Translation response schema. */\nexport const TranslateResponseSchema = z.object({\n translatedText: z.string(),\n detectedLanguage: DetectedLanguageSchema.optional(),\n alternatives: z.array(z.string()).optional(),\n})\n\n/** Translation response from the LibreTranslate API. */\nexport type TranslateResponse = z.infer<typeof TranslateResponseSchema>\n","/** Default URL for the LibreTranslate API. */\nexport const defaultBaseUrl = \"https://libretranslate.com\"\n","import type { DeepReadonly } from \"@cutticat/types\"\nimport type { TranslationOptions } from \"./types/TranslationOptions.js\"\nimport { TranslateResponseSchema, type TranslateResponse } from \"./schemas/TranslateResponse.js\"\nimport { defaultBaseUrl } from \"./constants.js\"\n\n/**\n * Translates text via the LibreTranslate API.\n *\n * @param options — translation parameters\n * @returns Promise with translation response\n * @throws {Error} On request failure (HTTP status included in message)\n *\n * @example\n * ```typescript\n * const result = await translate({\n * text: \"Hello, world!\",\n * source: \"en\",\n * target: \"ru\"\n * })\n * console.log(result.translatedText) // \"Привет, мир!\"\n * ```\n *\n * @example\n * ```typescript\n * // With language auto-detection\n * const result = await translate({\n * text: \"Bonjour\",\n * source: \"auto\",\n * target: \"en\"\n * })\n * console.log(result.translatedText) // \"Hello\"\n * console.log(result.detectedLanguage) // { code: \"fr\", confidence: 0.95 }\n * ```\n *\n * @example\n * ```typescript\n * // With custom API and key\n * const result = await translate({\n * text: \"Hello\",\n * source: \"en\",\n * target: \"es\",\n * baseUrl: \"https://translate.example.com\",\n * apiKey: \"your-api-key\"\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With custom fetch (e.g. for tests or proxy)\n * const result = await translate({\n * text: \"Hello\",\n * source: \"en\",\n * target: \"ru\",\n * fetch: myCustomFetch\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With alternative translations\n * const result = await translate({\n * text: \"Hello\",\n * source: \"en\",\n * target: \"ru\",\n * alternatives: 2\n * })\n * console.log(result.alternatives) // [\"Здравствуй\", \"Приветствую\"]\n * ```\n */\nexport async function translate({\n text,\n source,\n target,\n format = \"text\",\n alternatives = 0,\n apiKey,\n baseUrl = defaultBaseUrl,\n fetch = globalThis.fetch,\n}: DeepReadonly<TranslationOptions>): Promise<TranslateResponse> {\n // Validation\n\n if (alternatives < 0) throw new Error(\"Number of alternatives must be zero or positive\")\n if (!Number.isInteger(alternatives)) throw new Error(\"Number of alternatives must be an integer\")\n\n // Body creation\n\n const body: Record<string, unknown> = {\n q: text,\n source: source,\n target: target,\n }\n\n if (format != null) body.format = format\n if (alternatives > 0) body.alternatives = alternatives\n if (apiKey != null) body.api_key = apiKey\n\n // Request sending\n\n const url = new URL(\"/translate\", baseUrl)\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n })\n\n // Response processing\n\n if (!response.ok)\n throw new Error(`Failed to translate text: ${response.status} ${response.statusText}`)\n\n const json = await response.json()\n const data = TranslateResponseSchema.parse(json)\n\n return data\n}\n"],"mappings":";;;;AAGA,MAAa,yBAAyB,EAAE,OAAO;CAC7C,MAAM,EAAE,QAAQ;CAChB,YAAY,EAAE,QAAQ;CACvB,CAAC;;;;;ACFF,MAAa,0BAA0B,EAAE,OAAO;CAC9C,gBAAgB,EAAE,QAAQ;CAC1B,kBAAkB,uBAAuB,UAAU;CACnD,cAAc,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;CAC7C,CAAC;;;;;ACPF,MAAa,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACoE9B,eAAsB,UAAU,EAC9B,MACA,QACA,QACA,SAAS,QACT,eAAe,GACf,QACA,UAAU,gBACV,QAAQ,WAAW,SAC4C;AAG/D,KAAI,eAAe,EAAG,OAAM,IAAI,MAAM,kDAAkD;AACxF,KAAI,CAAC,OAAO,UAAU,aAAa,CAAE,OAAM,IAAI,MAAM,4CAA4C;CAIjG,MAAM,OAAgC;EACpC,GAAG;EACK;EACA;EACT;AAED,KAAI,UAAU,KAAM,MAAK,SAAS;AAClC,KAAI,eAAe,EAAG,MAAK,eAAe;AAC1C,KAAI,UAAU,KAAM,MAAK,UAAU;CAKnC,MAAM,WAAW,MAAM,MADX,IAAI,IAAI,cAAc,QAAQ,EACR;EAChC,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC3B,CAAC;AAIF,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,6BAA6B,SAAS,OAAO,GAAG,SAAS,aAAa;CAExF,MAAM,OAAO,MAAM,SAAS,MAAM;AAGlC,QAFa,wBAAwB,MAAM,KAAK"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@cutticat/libretranslate",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "1.0.3",
7
+ "description": "CuttiCat library for working with LibreTranslate",
8
+ "author": "Maksim Victorovich Fomin",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://gitflic.ru/project/cutticat-npm/libretranslate"
12
+ },
13
+ "homepage": "https://cutticat.com",
14
+ "keywords": [
15
+ "libretranslate",
16
+ "translation",
17
+ "i18n",
18
+ "localization",
19
+ "api-client",
20
+ "cutticat"
21
+ ],
22
+ "type": "module",
23
+ "engines": {
24
+ "node": ">=22.0.0"
25
+ },
26
+ "dependencies": {
27
+ "@cutticat/types": "^1.7.2",
28
+ "zod": "^4.4.3"
29
+ },
30
+ "devDependencies": {
31
+ "@biomejs/biome": "^2.4.16",
32
+ "@types/node": "^25.9.1",
33
+ "rimraf": "^6.1.3",
34
+ "tsdown": "0.19.0-beta.2",
35
+ "tsx": "^4.22.4",
36
+ "typescript": "^5.9.3"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.js",
42
+ "default": "./dist/index.js"
43
+ }
44
+ },
45
+ "files": [
46
+ "dist/**/*"
47
+ ],
48
+ "license": "MIT",
49
+ "scripts": {
50
+ "lint": "biome check .",
51
+ "lint:fix": "biome check --write .",
52
+ "lint:fix:unsafe": "biome check --write --unsafe .",
53
+ "format": "biome format --write .",
54
+ "format:check": "biome format .",
55
+ "clean": "rimraf ./dist",
56
+ "build": "tsc --noEmit -p ./tsconfig.build.json && tsdown",
57
+ "test": "tsx --test \"./tests/**/*.test.ts\"",
58
+ "test:watch": "tsx --test --watch \"./tests/**/*.test.ts\"",
59
+ "test:coverage": "tsx --test --experimental-test-coverage \"./tests/**/*.test.ts\""
60
+ }
61
+ }