@better-i18n/sdk 0.3.0 → 1.0.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 +3 -5
- package/package.json +1 -1
- package/src/client.ts +12 -6
- package/src/content-api.ts +34 -25
- package/src/types.ts +6 -5
package/README.md
CHANGED
|
@@ -23,8 +23,7 @@ npm install @better-i18n/sdk
|
|
|
23
23
|
import { createClient } from "@better-i18n/sdk";
|
|
24
24
|
|
|
25
25
|
const client = createClient({
|
|
26
|
-
|
|
27
|
-
project: "web-app",
|
|
26
|
+
project: "acme/web-app",
|
|
28
27
|
apiKey: process.env.BETTER_I18N_API_KEY!,
|
|
29
28
|
});
|
|
30
29
|
|
|
@@ -114,10 +113,9 @@ post.customFields.category; // string | null (typed!)
|
|
|
114
113
|
|
|
115
114
|
| Option | Required | Description |
|
|
116
115
|
| --- | --- | --- |
|
|
117
|
-
| `
|
|
118
|
-
| `project` | Yes | Project slug |
|
|
116
|
+
| `project` | Yes | Project identifier in `org/project` format (e.g., `"acme/web-app"`) |
|
|
119
117
|
| `apiKey` | Yes | API key from [dashboard](https://dash.better-i18n.com) |
|
|
120
|
-
| `apiBase` | No | API base URL (default: `https://
|
|
118
|
+
| `apiBase` | No | API base URL (default: `https://dash.better-i18n.com`) |
|
|
121
119
|
|
|
122
120
|
## Documentation
|
|
123
121
|
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClientConfig, ContentClient } from "./types";
|
|
2
2
|
import { createContentAPIClient } from "./content-api";
|
|
3
3
|
|
|
4
|
-
const DEFAULT_API_BASE = "https://
|
|
4
|
+
const DEFAULT_API_BASE = "https://content.better-i18n.com";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a Better i18n content client.
|
|
@@ -12,8 +12,7 @@ const DEFAULT_API_BASE = "https://api.better-i18n.com";
|
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
14
14
|
* const client = createClient({
|
|
15
|
-
*
|
|
16
|
-
* project: "web-app",
|
|
15
|
+
* project: "acme/web-app",
|
|
17
16
|
* apiKey: "bi18n_...",
|
|
18
17
|
* });
|
|
19
18
|
*
|
|
@@ -23,16 +22,23 @@ const DEFAULT_API_BASE = "https://api.better-i18n.com";
|
|
|
23
22
|
* ```
|
|
24
23
|
*/
|
|
25
24
|
export function createClient(config: ClientConfig): ContentClient {
|
|
26
|
-
const { org, project } = config;
|
|
27
25
|
const apiBase = (config.apiBase || DEFAULT_API_BASE).replace(/\/$/, "");
|
|
28
26
|
|
|
27
|
+
const parts = config.project.split("/");
|
|
28
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Invalid project format "${config.project}". Expected "org/project" (e.g., "acme/web-app").`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const [org, project] = parts;
|
|
34
|
+
|
|
29
35
|
if (!config.apiKey) {
|
|
30
36
|
throw new Error(
|
|
31
37
|
"API key is required for content API access.\n" +
|
|
32
38
|
"Set apiKey in your client config:\n\n" +
|
|
33
|
-
' createClient({
|
|
39
|
+
' createClient({ project: "acme/web-app", apiKey: "bi18n_..." })',
|
|
34
40
|
);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
return createContentAPIClient(apiBase, org, project, config.apiKey);
|
|
43
|
+
return createContentAPIClient(apiBase, org, project, config.apiKey, config.debug);
|
|
38
44
|
}
|
package/src/content-api.ts
CHANGED
|
@@ -15,29 +15,49 @@ import type {
|
|
|
15
15
|
* Requires an API key for authentication via the `x-api-key` header.
|
|
16
16
|
*
|
|
17
17
|
* URL patterns:
|
|
18
|
-
* - Models: `{apiBase}/
|
|
19
|
-
* - Entries: `{apiBase}/
|
|
20
|
-
* - Entry: `{apiBase}/
|
|
18
|
+
* - Models: `{apiBase}/v1/content/{org}/{project}/models`
|
|
19
|
+
* - Entries: `{apiBase}/v1/content/{org}/{project}/models/{model}/entries`
|
|
20
|
+
* - Entry: `{apiBase}/v1/content/{org}/{project}/models/{model}/entries/{slug}`
|
|
21
21
|
*/
|
|
22
22
|
export function createContentAPIClient(
|
|
23
23
|
apiBase: string,
|
|
24
24
|
org: string,
|
|
25
25
|
project: string,
|
|
26
26
|
apiKey: string,
|
|
27
|
+
debug = false,
|
|
27
28
|
): ContentClient {
|
|
28
|
-
const base = `${apiBase}/
|
|
29
|
+
const base = `${apiBase}/v1/content/${org}/${project}`;
|
|
29
30
|
const headers: Record<string, string> = {
|
|
30
31
|
"x-api-key": apiKey,
|
|
31
32
|
"content-type": "application/json",
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
const log = debug
|
|
36
|
+
? (...args: unknown[]) => console.log("[better-i18n]", ...args)
|
|
37
|
+
: () => {};
|
|
38
|
+
|
|
39
|
+
if (debug) {
|
|
40
|
+
log("Client initialized", { apiBase, org, project, base });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function request<T>(url: string, label: string): Promise<{ res: Response; data: T }> {
|
|
44
|
+
log(`→ ${label}`, url);
|
|
45
|
+
const res = await fetch(url, { headers });
|
|
46
|
+
log(`← ${res.status} ${res.statusText}`);
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body = await res.text().catch(() => "");
|
|
49
|
+
log(" Error body:", body);
|
|
50
|
+
throw new Error(`API error ${label}: ${res.status} ${body}`);
|
|
51
|
+
}
|
|
52
|
+
const data = await res.json() as T;
|
|
53
|
+
log(" Response:", JSON.stringify(data).slice(0, 500));
|
|
54
|
+
return { res, data };
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
return {
|
|
35
58
|
async getModels(): Promise<ContentModel[]> {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
throw new Error(`API error fetching models: ${res.status}`);
|
|
39
|
-
}
|
|
40
|
-
return res.json();
|
|
59
|
+
const { data } = await request<ContentModel[]>(`${base}/models`, "getModels");
|
|
60
|
+
return data;
|
|
41
61
|
},
|
|
42
62
|
|
|
43
63
|
async getEntries(
|
|
@@ -53,16 +73,10 @@ export function createContentAPIClient(
|
|
|
53
73
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
54
74
|
const qs = params.toString() ? `?${params}` : "";
|
|
55
75
|
|
|
56
|
-
const
|
|
76
|
+
const { data } = await request<{ items: ContentEntryListItem[]; total: number; hasMore: boolean }>(
|
|
57
77
|
`${base}/models/${modelSlug}/entries${qs}`,
|
|
58
|
-
{
|
|
78
|
+
`getEntries(${modelSlug})`,
|
|
59
79
|
);
|
|
60
|
-
if (!res.ok) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
`API error fetching entries for ${modelSlug}: ${res.status}`,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
const data = await res.json() as { items: ContentEntryListItem[]; total: number; hasMore: boolean };
|
|
66
80
|
return {
|
|
67
81
|
items: data.items,
|
|
68
82
|
total: data.total,
|
|
@@ -79,16 +93,11 @@ export function createContentAPIClient(
|
|
|
79
93
|
if (options?.language) params.set("language", options.language);
|
|
80
94
|
const qs = params.toString() ? `?${params}` : "";
|
|
81
95
|
|
|
82
|
-
const
|
|
96
|
+
const { data } = await request<ContentEntry<CF>>(
|
|
83
97
|
`${base}/models/${modelSlug}/entries/${entrySlug}${qs}`,
|
|
84
|
-
{
|
|
98
|
+
`getEntry(${modelSlug}/${entrySlug})`,
|
|
85
99
|
);
|
|
86
|
-
|
|
87
|
-
throw new Error(
|
|
88
|
-
`API error fetching entry ${modelSlug}/${entrySlug}: ${res.status}`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
return res.json();
|
|
100
|
+
return data;
|
|
92
101
|
},
|
|
93
102
|
};
|
|
94
103
|
}
|
package/src/types.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
/** Configuration for creating a Better i18n content client. */
|
|
4
4
|
export interface ClientConfig {
|
|
5
|
-
/**
|
|
6
|
-
org: string;
|
|
7
|
-
/** Project slug (e.g., "web-app"). */
|
|
5
|
+
/** Project identifier in `org/project` format (e.g., "acme-corp/web-app"). Same as the dashboard URL path. */
|
|
8
6
|
project: string;
|
|
9
7
|
/** API key for authenticating content requests. Required. */
|
|
10
8
|
apiKey: string;
|
|
11
|
-
/** REST API base URL. Defaults to `https://
|
|
9
|
+
/** REST API base URL. Defaults to `https://dash.better-i18n.com`. */
|
|
12
10
|
apiBase?: string;
|
|
11
|
+
/** Enable debug logging to see request URLs, headers, and responses. */
|
|
12
|
+
debug?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// ─── Content Types ───────────────────────────────────────────────────
|
|
@@ -41,7 +41,8 @@ export interface ContentEntry<CF extends Record<string, string | null> = Record<
|
|
|
41
41
|
// Localized content
|
|
42
42
|
title: string;
|
|
43
43
|
excerpt: string | null;
|
|
44
|
-
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
body: Record<string, any> | null;
|
|
45
46
|
bodyHtml: string | null;
|
|
46
47
|
bodyMarkdown: string | null;
|
|
47
48
|
metaTitle: string | null;
|