langbly 0.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/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/client.d.ts +71 -0
- package/dist/client.js +179 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Langbly
|
|
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,122 @@
|
|
|
1
|
+
# langbly-js
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/langbly)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Official JavaScript/TypeScript SDK for the [Langbly](https://langbly.com) translation API — a drop-in replacement for Google Translate v2, powered by LLMs.
|
|
8
|
+
|
|
9
|
+
**5-10x cheaper than Google Translate** · **Better quality** · **Switch in one PR**
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install langbly
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Langbly } from "langbly";
|
|
21
|
+
|
|
22
|
+
const client = new Langbly({ apiKey: "your-api-key" });
|
|
23
|
+
|
|
24
|
+
// Translate text
|
|
25
|
+
const result = await client.translate("Hello world", { target: "nl" });
|
|
26
|
+
console.log(result.text); // "Hallo wereld"
|
|
27
|
+
|
|
28
|
+
// Batch translate
|
|
29
|
+
const results = await client.translate(["Hello", "Goodbye"], { target: "nl" });
|
|
30
|
+
results.forEach((r) => console.log(r.text));
|
|
31
|
+
|
|
32
|
+
// Detect language
|
|
33
|
+
const detection = await client.detect("Bonjour le monde");
|
|
34
|
+
console.log(detection.language); // "fr"
|
|
35
|
+
|
|
36
|
+
// List supported languages
|
|
37
|
+
const languages = await client.languages({ target: "en" });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Migrate from Google Translate
|
|
41
|
+
|
|
42
|
+
Already using `@google-cloud/translate`? Switching takes 2 minutes:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Before (Google Translate)
|
|
46
|
+
import { Translate } from "@google-cloud/translate/build/src/v2";
|
|
47
|
+
const client = new Translate();
|
|
48
|
+
const [translation] = await client.translate("Hello", "nl");
|
|
49
|
+
|
|
50
|
+
// After (Langbly) — same concepts, better translations, 5x cheaper
|
|
51
|
+
import { Langbly } from "langbly";
|
|
52
|
+
const client = new Langbly({ apiKey: "your-key" });
|
|
53
|
+
const result = await client.translate("Hello", { target: "nl" });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
→ Full migration guide: [langbly.com/docs/migrate-google](https://langbly.com/docs/migrate-google)
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Google Translate v2 API compatible** — same endpoint format
|
|
61
|
+
- **Zero dependencies** — uses native `fetch`
|
|
62
|
+
- **Full TypeScript types** — interfaces for all request/response shapes
|
|
63
|
+
- **Auto-retry** — exponential backoff on 429/5xx with Retry-After support
|
|
64
|
+
- **Typed errors** — `RateLimitError`, `AuthenticationError`, `LangblyError`
|
|
65
|
+
- **Batch translation** — translate multiple texts in one request
|
|
66
|
+
- **Language detection** — automatic source language identification
|
|
67
|
+
- **HTML support** — translate HTML while preserving tags
|
|
68
|
+
|
|
69
|
+
## Error Handling
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Langbly, RateLimitError, AuthenticationError } from "langbly";
|
|
73
|
+
|
|
74
|
+
const client = new Langbly({ apiKey: "your-key" });
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = await client.translate("Hello", { target: "nl" });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err instanceof AuthenticationError) {
|
|
80
|
+
console.error("Invalid API key");
|
|
81
|
+
} else if (err instanceof RateLimitError) {
|
|
82
|
+
console.error(`Rate limited — retry after ${err.retryAfter}s`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
### `new Langbly(options)`
|
|
90
|
+
|
|
91
|
+
Create a client instance.
|
|
92
|
+
|
|
93
|
+
- `apiKey` (string): Your Langbly API key — [get one free](https://langbly.com/signup)
|
|
94
|
+
- `baseUrl` (string, optional): Override API URL (default: `https://api.langbly.com`)
|
|
95
|
+
- `timeout` (number, optional): Request timeout in ms (default: 30000)
|
|
96
|
+
- `maxRetries` (number, optional): Retries for transient errors (default: 2)
|
|
97
|
+
|
|
98
|
+
### `client.translate(text, options)`
|
|
99
|
+
|
|
100
|
+
- `text` (string | string[]): Text(s) to translate
|
|
101
|
+
- `options.target` (string): Target language code
|
|
102
|
+
- `options.source` (string, optional): Source language code
|
|
103
|
+
- `options.format` ("text" | "html", optional): Input format
|
|
104
|
+
|
|
105
|
+
### `client.detect(text)`
|
|
106
|
+
|
|
107
|
+
- `text` (string): Text to analyze
|
|
108
|
+
|
|
109
|
+
### `client.languages(options?)`
|
|
110
|
+
|
|
111
|
+
- `options.target` (string, optional): Language code for names
|
|
112
|
+
|
|
113
|
+
## Links
|
|
114
|
+
|
|
115
|
+
- [Website](https://langbly.com)
|
|
116
|
+
- [Documentation](https://langbly.com/docs)
|
|
117
|
+
- [Compare: Langbly vs Google vs DeepL](https://langbly.com/compare)
|
|
118
|
+
- [Python SDK](https://github.com/Langbly/langbly-python)
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Langbly — Official JavaScript/TypeScript SDK for the Langbly translation API.
|
|
3
|
+
*
|
|
4
|
+
* A drop-in replacement for Google Translate v2 — powered by LLMs.
|
|
5
|
+
*/
|
|
6
|
+
export interface LangblyOptions {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
/** Number of retries for transient errors (429, 5xx). Default: 2. */
|
|
11
|
+
maxRetries?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface TranslateOptions {
|
|
14
|
+
target: string;
|
|
15
|
+
source?: string;
|
|
16
|
+
format?: "text" | "html";
|
|
17
|
+
}
|
|
18
|
+
export interface Translation {
|
|
19
|
+
text: string;
|
|
20
|
+
source: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Detection {
|
|
24
|
+
language: string;
|
|
25
|
+
confidence: number;
|
|
26
|
+
}
|
|
27
|
+
export interface Language {
|
|
28
|
+
code: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class LangblyError extends Error {
|
|
32
|
+
status: number;
|
|
33
|
+
code: string;
|
|
34
|
+
constructor(message: string, status?: number, code?: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class RateLimitError extends LangblyError {
|
|
37
|
+
retryAfter: number | null;
|
|
38
|
+
constructor(message: string, retryAfter?: number | null);
|
|
39
|
+
}
|
|
40
|
+
export declare class AuthenticationError extends LangblyError {
|
|
41
|
+
constructor(message: string);
|
|
42
|
+
}
|
|
43
|
+
export declare class Langbly {
|
|
44
|
+
private apiKey;
|
|
45
|
+
private baseUrl;
|
|
46
|
+
private timeout;
|
|
47
|
+
private maxRetries;
|
|
48
|
+
constructor(options: LangblyOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Translate text to the target language.
|
|
51
|
+
*/
|
|
52
|
+
translate(text: string, options: TranslateOptions): Promise<Translation>;
|
|
53
|
+
translate(text: string[], options: TranslateOptions): Promise<Translation[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Detect the language of text.
|
|
56
|
+
*/
|
|
57
|
+
detect(text: string): Promise<Detection>;
|
|
58
|
+
/**
|
|
59
|
+
* List supported languages.
|
|
60
|
+
*/
|
|
61
|
+
languages(options?: {
|
|
62
|
+
target?: string;
|
|
63
|
+
}): Promise<Language[]>;
|
|
64
|
+
private postWithRetry;
|
|
65
|
+
private fetchWithRetry;
|
|
66
|
+
private throwForStatus;
|
|
67
|
+
private parseRetryAfter;
|
|
68
|
+
private getRetryDelay;
|
|
69
|
+
private backoffDelay;
|
|
70
|
+
private sleep;
|
|
71
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Langbly — Official JavaScript/TypeScript SDK for the Langbly translation API.
|
|
3
|
+
*
|
|
4
|
+
* A drop-in replacement for Google Translate v2 — powered by LLMs.
|
|
5
|
+
*/
|
|
6
|
+
export class LangblyError extends Error {
|
|
7
|
+
constructor(message, status = 0, code = "") {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "LangblyError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class RateLimitError extends LangblyError {
|
|
15
|
+
constructor(message, retryAfter = null) {
|
|
16
|
+
super(message, 429, "RATE_LIMITED");
|
|
17
|
+
this.name = "RateLimitError";
|
|
18
|
+
this.retryAfter = retryAfter;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class AuthenticationError extends LangblyError {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message, 401, "UNAUTHENTICATED");
|
|
24
|
+
this.name = "AuthenticationError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const RETRIABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
28
|
+
export class Langbly {
|
|
29
|
+
constructor(options) {
|
|
30
|
+
if (!options.apiKey) {
|
|
31
|
+
throw new Error("apiKey is required");
|
|
32
|
+
}
|
|
33
|
+
this.apiKey = options.apiKey;
|
|
34
|
+
this.baseUrl = (options.baseUrl ?? "https://api.langbly.com").replace(/\/$/, "");
|
|
35
|
+
this.timeout = options.timeout ?? 30000;
|
|
36
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
37
|
+
}
|
|
38
|
+
async translate(text, options) {
|
|
39
|
+
const q = Array.isArray(text) ? text : [text];
|
|
40
|
+
const body = { q, target: options.target };
|
|
41
|
+
if (options.source)
|
|
42
|
+
body.source = options.source;
|
|
43
|
+
if (options.format)
|
|
44
|
+
body.format = options.format;
|
|
45
|
+
const data = await this.postWithRetry("/language/translate/v2", body);
|
|
46
|
+
const translations = data.data.translations.map((item) => ({
|
|
47
|
+
text: item.translatedText,
|
|
48
|
+
source: item.detectedSourceLanguage ?? options.source ?? "",
|
|
49
|
+
model: item.model,
|
|
50
|
+
}));
|
|
51
|
+
return Array.isArray(text) ? translations : translations[0];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Detect the language of text.
|
|
55
|
+
*/
|
|
56
|
+
async detect(text) {
|
|
57
|
+
const data = await this.postWithRetry("/language/translate/v2/detect", {
|
|
58
|
+
q: text,
|
|
59
|
+
});
|
|
60
|
+
const det = data.data.detections[0][0];
|
|
61
|
+
return {
|
|
62
|
+
language: det.language,
|
|
63
|
+
confidence: det.confidence ?? 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* List supported languages.
|
|
68
|
+
*/
|
|
69
|
+
async languages(options) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
if (options?.target)
|
|
72
|
+
params.set("target", options.target);
|
|
73
|
+
const qs = params.toString();
|
|
74
|
+
const url = `/language/translate/v2/languages${qs ? `?${qs}` : ""}`;
|
|
75
|
+
const resp = await this.fetchWithRetry(`${this.baseUrl}${url}`, {
|
|
76
|
+
method: "GET",
|
|
77
|
+
});
|
|
78
|
+
const data = await resp.json();
|
|
79
|
+
return data.data.languages.map((lang) => ({
|
|
80
|
+
code: lang.language,
|
|
81
|
+
name: lang.name,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
async postWithRetry(path, body) {
|
|
85
|
+
const resp = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "Content-Type": "application/json" },
|
|
88
|
+
body: JSON.stringify(body),
|
|
89
|
+
});
|
|
90
|
+
return resp.json();
|
|
91
|
+
}
|
|
92
|
+
async fetchWithRetry(url, init) {
|
|
93
|
+
const headers = {
|
|
94
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
95
|
+
"User-Agent": "langbly-js/0.1.0",
|
|
96
|
+
...init.headers,
|
|
97
|
+
};
|
|
98
|
+
let lastError;
|
|
99
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
100
|
+
let resp;
|
|
101
|
+
try {
|
|
102
|
+
resp = await fetch(url, {
|
|
103
|
+
...init,
|
|
104
|
+
headers,
|
|
105
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
// Network error or timeout
|
|
110
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
111
|
+
if (attempt < this.maxRetries) {
|
|
112
|
+
await this.sleep(this.backoffDelay(attempt));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
throw new LangblyError(`Request failed after ${this.maxRetries + 1} attempts: ${lastError.message}`, 0, "CONNECTION_ERROR");
|
|
116
|
+
}
|
|
117
|
+
if (resp.ok) {
|
|
118
|
+
return resp;
|
|
119
|
+
}
|
|
120
|
+
// Non-retriable error — throw immediately
|
|
121
|
+
if (!RETRIABLE_STATUS_CODES.has(resp.status)) {
|
|
122
|
+
await this.throwForStatus(resp);
|
|
123
|
+
}
|
|
124
|
+
// Retriable error — retry if we have attempts left
|
|
125
|
+
if (attempt < this.maxRetries) {
|
|
126
|
+
const delay = this.getRetryDelay(resp, attempt);
|
|
127
|
+
await this.sleep(delay);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// Final attempt failed
|
|
131
|
+
await this.throwForStatus(resp);
|
|
132
|
+
}
|
|
133
|
+
// Should not reach here
|
|
134
|
+
throw lastError ?? new LangblyError("Request failed");
|
|
135
|
+
}
|
|
136
|
+
async throwForStatus(resp) {
|
|
137
|
+
let message = resp.statusText;
|
|
138
|
+
let code = "";
|
|
139
|
+
try {
|
|
140
|
+
const err = await resp.json();
|
|
141
|
+
message = err?.error?.message ?? message;
|
|
142
|
+
code = err?.error?.status ?? "";
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// ignore parse errors
|
|
146
|
+
}
|
|
147
|
+
if (resp.status === 401) {
|
|
148
|
+
throw new AuthenticationError(message);
|
|
149
|
+
}
|
|
150
|
+
if (resp.status === 429) {
|
|
151
|
+
const retryAfter = this.parseRetryAfter(resp);
|
|
152
|
+
throw new RateLimitError(message, retryAfter);
|
|
153
|
+
}
|
|
154
|
+
throw new LangblyError(message, resp.status, code);
|
|
155
|
+
}
|
|
156
|
+
parseRetryAfter(resp) {
|
|
157
|
+
const header = resp.headers.get("retry-after");
|
|
158
|
+
if (!header)
|
|
159
|
+
return null;
|
|
160
|
+
const value = Number(header);
|
|
161
|
+
return Number.isFinite(value) ? value : null;
|
|
162
|
+
}
|
|
163
|
+
getRetryDelay(resp, attempt) {
|
|
164
|
+
const retryAfter = resp.headers.get("retry-after");
|
|
165
|
+
if (retryAfter) {
|
|
166
|
+
const value = Number(retryAfter);
|
|
167
|
+
if (Number.isFinite(value)) {
|
|
168
|
+
return Math.min(value * 1000, 30000);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return Math.min(500 * 2 ** attempt, 10000);
|
|
172
|
+
}
|
|
173
|
+
backoffDelay(attempt) {
|
|
174
|
+
return Math.min(500 * 2 ** attempt, 10000);
|
|
175
|
+
}
|
|
176
|
+
sleep(ms) {
|
|
177
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
178
|
+
}
|
|
179
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Langbly, LangblyError, RateLimitError, AuthenticationError, } from "./client.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "langbly",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official JavaScript/TypeScript SDK for the Langbly translation API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"translation",
|
|
22
|
+
"api",
|
|
23
|
+
"langbly",
|
|
24
|
+
"google-translate",
|
|
25
|
+
"i18n",
|
|
26
|
+
"localization",
|
|
27
|
+
"llm"
|
|
28
|
+
],
|
|
29
|
+
"author": "Jasper de Winter <jasper@langbly.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/Langbly/langbly-js.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://langbly.com",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"typescript": "^5.4",
|
|
38
|
+
"vitest": "^1.6"
|
|
39
|
+
}
|
|
40
|
+
}
|