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 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
+ [![npm](https://img.shields.io/npm/v/langbly)](https://www.npmjs.com/package/langbly)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-first--class-blue)](https://www.typescriptlang.org/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export { Langbly, LangblyError, RateLimitError, AuthenticationError, } from "./client.js";
2
+ export type { LangblyOptions, TranslateOptions, Translation, Detection, Language, } from "./client.js";
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
+ }