@shaferllc/keel 0.66.0 → 0.74.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/AGENTS.md +169 -0
- package/README.md +44 -6
- package/bin/keel-mcp.mjs +9 -0
- package/dist/api/api.config.stub +9 -0
- package/dist/api/config.d.ts +13 -0
- package/dist/api/config.js +14 -0
- package/dist/api/index.d.ts +16 -0
- package/dist/api/index.js +13 -0
- package/dist/api/provider.d.ts +10 -0
- package/dist/api/provider.js +17 -0
- package/dist/api/query.d.ts +35 -0
- package/dist/api/query.js +42 -0
- package/dist/api/resource.d.ts +91 -0
- package/dist/api/resource.js +188 -0
- package/dist/core/application.d.ts +5 -5
- package/dist/core/application.js +8 -2
- package/dist/core/cache.d.ts +81 -5
- package/dist/core/cache.js +188 -23
- package/dist/core/cli/stubs.d.ts +26 -0
- package/dist/core/cli/stubs.js +225 -0
- package/dist/core/console-prompt.d.ts +79 -0
- package/dist/core/console-prompt.js +239 -0
- package/dist/core/console-ui.d.ts +96 -0
- package/dist/core/console-ui.js +187 -0
- package/dist/core/console.d.ts +188 -0
- package/dist/core/console.js +395 -0
- package/dist/core/database.d.ts +70 -1
- package/dist/core/database.js +174 -15
- package/dist/core/env.d.ts +96 -0
- package/dist/core/env.js +140 -0
- package/dist/core/events.d.ts +129 -5
- package/dist/core/events.js +165 -7
- package/dist/core/health.d.ts +141 -0
- package/dist/core/health.js +226 -0
- package/dist/core/helpers.d.ts +9 -3
- package/dist/core/helpers.js +11 -3
- package/dist/core/http/kernel.d.ts +2 -0
- package/dist/core/http/kernel.js +48 -0
- package/dist/core/http/router.d.ts +5 -5
- package/dist/core/http/router.js +5 -5
- package/dist/core/i18n.d.ts +162 -0
- package/dist/core/i18n.js +472 -0
- package/dist/core/index.d.ts +39 -13
- package/dist/core/index.js +19 -6
- package/dist/core/instrumentation.d.ts +113 -0
- package/dist/core/instrumentation.js +52 -0
- package/dist/core/lock.d.ts +139 -0
- package/dist/core/lock.js +215 -0
- package/dist/core/logger.d.ts +89 -4
- package/dist/core/logger.js +167 -22
- package/dist/core/mail.d.ts +128 -7
- package/dist/core/mail.js +264 -16
- package/dist/core/notification.js +10 -1
- package/dist/core/package.d.ts +120 -0
- package/dist/core/package.js +169 -0
- package/dist/core/pages.d.ts +108 -0
- package/dist/core/pages.js +199 -0
- package/dist/core/queue.d.ts +134 -9
- package/dist/core/queue.js +325 -14
- package/dist/core/repl.d.ts +33 -0
- package/dist/core/repl.js +88 -0
- package/dist/core/scheduler.js +6 -0
- package/dist/core/social.d.ts +4 -4
- package/dist/core/social.js +4 -4
- package/dist/core/storage.d.ts +159 -6
- package/dist/core/storage.js +299 -7
- package/dist/core/telemetry.d.ts +208 -0
- package/dist/core/telemetry.js +383 -0
- package/dist/core/template.d.ts +2 -3
- package/dist/core/template.js +2 -3
- package/dist/core/testing.d.ts +170 -1
- package/dist/core/testing.js +504 -2
- package/dist/db/d1.js +13 -0
- package/dist/db/pg.d.ts +13 -0
- package/dist/db/pg.js +46 -4
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +355 -0
- package/dist/openapi/config.d.ts +28 -0
- package/dist/openapi/config.js +25 -0
- package/dist/openapi/doc.d.ts +40 -0
- package/dist/openapi/doc.js +20 -0
- package/dist/openapi/export.d.ts +8 -0
- package/dist/openapi/export.js +19 -0
- package/dist/openapi/gate.d.ts +15 -0
- package/dist/openapi/gate.js +27 -0
- package/dist/openapi/index.d.ts +19 -0
- package/dist/openapi/index.js +15 -0
- package/dist/openapi/openapi.config.stub +29 -0
- package/dist/openapi/provider.d.ts +18 -0
- package/dist/openapi/provider.js +35 -0
- package/dist/openapi/routes.d.ts +9 -0
- package/dist/openapi/routes.js +23 -0
- package/dist/openapi/spec.d.ts +23 -0
- package/dist/openapi/spec.js +132 -0
- package/dist/openapi/ui.d.ts +8 -0
- package/dist/openapi/ui.js +31 -0
- package/dist/openapi/zod.d.ts +12 -0
- package/dist/openapi/zod.js +46 -0
- package/dist/watch/config.d.ts +33 -0
- package/dist/watch/config.js +38 -0
- package/dist/watch/entry.d.ts +53 -0
- package/dist/watch/entry.js +105 -0
- package/dist/watch/gate.d.ts +20 -0
- package/dist/watch/gate.js +32 -0
- package/dist/watch/index.d.ts +21 -0
- package/dist/watch/index.js +17 -0
- package/dist/watch/migration.d.ts +7 -0
- package/dist/watch/migration.js +20 -0
- package/dist/watch/provider.d.ts +22 -0
- package/dist/watch/provider.js +58 -0
- package/dist/watch/prune.d.ts +11 -0
- package/dist/watch/prune.js +20 -0
- package/dist/watch/recorder.d.ts +24 -0
- package/dist/watch/recorder.js +39 -0
- package/dist/watch/routes.d.ts +13 -0
- package/dist/watch/routes.js +55 -0
- package/dist/watch/store.d.ts +54 -0
- package/dist/watch/store.js +158 -0
- package/dist/watch/ui/dist/watch.css +1 -0
- package/dist/watch/ui/dist/watch.js +555 -0
- package/dist/watch/ui-shell.d.ts +12 -0
- package/dist/watch/ui-shell.js +24 -0
- package/dist/watch/watch.config.stub +47 -0
- package/dist/watch/watchers.d.ts +12 -0
- package/dist/watch/watchers.js +156 -0
- package/docs/ai-manifest.json +3206 -0
- package/docs/ai.md +128 -0
- package/docs/api-resources.md +118 -0
- package/docs/architecture.md +331 -0
- package/docs/authentication.md +453 -0
- package/docs/authorization.md +167 -0
- package/docs/broadcasting.md +137 -0
- package/docs/broker.md +500 -0
- package/docs/cache.md +558 -0
- package/docs/configuration.md +385 -0
- package/docs/console.md +536 -0
- package/docs/container.md +467 -0
- package/docs/controllers.md +265 -0
- package/docs/cors.md +51 -0
- package/docs/database.md +631 -0
- package/docs/debugging.md +129 -0
- package/docs/decorators.md +127 -0
- package/docs/errors.md +395 -0
- package/docs/events.md +496 -0
- package/docs/examples/architecture-app.ts +27 -0
- package/docs/examples/authentication.ts +61 -0
- package/docs/examples/authorization.ts +79 -0
- package/docs/examples/broadcasting.ts +60 -0
- package/docs/examples/broker-cache-validate.ts +34 -0
- package/docs/examples/broker-fault-tolerance.ts +29 -0
- package/docs/examples/broker-middleware.ts +27 -0
- package/docs/examples/broker.ts +203 -0
- package/docs/examples/cache.ts +222 -0
- package/docs/examples/configuration.ts +121 -0
- package/docs/examples/console.ts +134 -0
- package/docs/examples/container.ts +134 -0
- package/docs/examples/controllers.ts +86 -0
- package/docs/examples/database.ts +208 -0
- package/docs/examples/debugging.ts +41 -0
- package/docs/examples/decorators.ts +40 -0
- package/docs/examples/errors.ts +121 -0
- package/docs/examples/events.ts +204 -0
- package/docs/examples/factories.ts +84 -0
- package/docs/examples/hashing.ts +71 -0
- package/docs/examples/health.ts +94 -0
- package/docs/examples/helpers.ts +171 -0
- package/docs/examples/hooks.ts +54 -0
- package/docs/examples/i18n.ts +117 -0
- package/docs/examples/inertia.ts +81 -0
- package/docs/examples/locks.ts +120 -0
- package/docs/examples/logger.ts +166 -0
- package/docs/examples/mail.ts +263 -0
- package/docs/examples/middleware.ts +119 -0
- package/docs/examples/migrations.ts +126 -0
- package/docs/examples/models.ts +239 -0
- package/docs/examples/notification.ts +124 -0
- package/docs/examples/pages.ts +82 -0
- package/docs/examples/providers.ts +123 -0
- package/docs/examples/queues.ts +254 -0
- package/docs/examples/rate-limiting.ts +42 -0
- package/docs/examples/redis.ts +99 -0
- package/docs/examples/request-response.ts +197 -0
- package/docs/examples/routing.ts +186 -0
- package/docs/examples/scheduling.ts +62 -0
- package/docs/examples/sessions.ts +102 -0
- package/docs/examples/static-files.ts +63 -0
- package/docs/examples/storage.ts +132 -0
- package/docs/examples/telemetry.ts +127 -0
- package/docs/examples/templates.ts +58 -0
- package/docs/examples/testing.ts +215 -0
- package/docs/examples/transformer.ts +141 -0
- package/docs/examples/transformers.ts +49 -0
- package/docs/examples/url-builder.ts +86 -0
- package/docs/examples/validation.ts +102 -0
- package/docs/examples/views.tsx +62 -0
- package/docs/examples/vite.ts +106 -0
- package/docs/factories.md +166 -0
- package/docs/getting-started.md +290 -0
- package/docs/hashing.md +259 -0
- package/docs/health.md +225 -0
- package/docs/helpers.md +347 -0
- package/docs/hono.md +186 -0
- package/docs/hooks.md +118 -0
- package/docs/i18n.md +302 -0
- package/docs/inertia.md +241 -0
- package/docs/locks.md +323 -0
- package/docs/logger.md +436 -0
- package/docs/mail.md +751 -0
- package/docs/middleware.md +425 -0
- package/docs/migrations.md +476 -0
- package/docs/models.md +810 -0
- package/docs/notifications.md +474 -0
- package/docs/openapi.md +111 -0
- package/docs/packages.md +118 -0
- package/docs/pages.md +217 -0
- package/docs/providers.md +363 -0
- package/docs/queues.md +679 -0
- package/docs/rate-limiting.md +155 -0
- package/docs/redis.md +178 -0
- package/docs/request-response.md +953 -0
- package/docs/routing.md +804 -0
- package/docs/scheduling.md +110 -0
- package/docs/security.md +85 -0
- package/docs/sessions.md +354 -0
- package/docs/social-auth.md +174 -0
- package/docs/static-files.md +211 -0
- package/docs/storage.md +452 -0
- package/docs/telemetry.md +263 -0
- package/docs/templates.md +314 -0
- package/docs/testing.md +376 -0
- package/docs/transformers.md +381 -0
- package/docs/url-builder.md +295 -0
- package/docs/validation.md +288 -0
- package/docs/views.md +267 -0
- package/docs/vite.md +434 -0
- package/docs/watch.md +118 -0
- package/llms-full.txt +19816 -0
- package/llms.txt +127 -0
- package/package.json +42 -7
package/dist/core/http/kernel.js
CHANGED
|
@@ -8,6 +8,7 @@ import { contextStorage } from "hono/context-storage";
|
|
|
8
8
|
import { Config } from "../config.js";
|
|
9
9
|
import { HttpException, NotFoundException, ValidationException, STATUS_TEXT, } from "../exceptions.js";
|
|
10
10
|
import { Router } from "./router.js";
|
|
11
|
+
import { instrument, runRequest, newRequestId, currentRequestId } from "../instrumentation.js";
|
|
11
12
|
/** Per-request stash of subdomain params, keyed by the raw Request. */
|
|
12
13
|
const SUBDOMAINS = new WeakMap();
|
|
13
14
|
/** Compile a host pattern like ":tenant.example.com" into a matcher. */
|
|
@@ -96,6 +97,26 @@ export class HttpKernel {
|
|
|
96
97
|
c.set("subdomains", subs);
|
|
97
98
|
await next();
|
|
98
99
|
});
|
|
100
|
+
// Open a request scope: a request id everything downstream (queries, logs,
|
|
101
|
+
// jobs) can attribute itself to, plus timing. Emitting `request.handled` and
|
|
102
|
+
// `exception` from here — inside the scope — is what lets a watcher tie a
|
|
103
|
+
// request to everything that happened within it. A no-op stream when nobody
|
|
104
|
+
// is listening, so this stays cheap for apps that don't instrument.
|
|
105
|
+
hono.use("*", async (c, next) => {
|
|
106
|
+
const id = newRequestId();
|
|
107
|
+
const start = Date.now();
|
|
108
|
+
try {
|
|
109
|
+
await runRequest(id, () => next());
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
// Errors thrown *outside* Hono's per-request handling (rare) land here;
|
|
113
|
+
// the common case (a handler throwing) is caught by Hono and reaches
|
|
114
|
+
// `handle()` instead, which is where `exception` is emitted.
|
|
115
|
+
this.reportRequest(c, id, start, err instanceof HttpException ? err.status : 500);
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
this.reportRequest(c, id, start, c.res.status);
|
|
119
|
+
});
|
|
99
120
|
for (const mw of this.middleware) {
|
|
100
121
|
hono.use("*", mw);
|
|
101
122
|
}
|
|
@@ -128,7 +149,34 @@ export class HttpKernel {
|
|
|
128
149
|
hono.onError((err, c) => this.handle(err, c));
|
|
129
150
|
return hono;
|
|
130
151
|
}
|
|
152
|
+
/** Emit `request.handled` with method, path, status, timing, and headers. */
|
|
153
|
+
reportRequest(c, id, start, status) {
|
|
154
|
+
const url = new URL(c.req.url);
|
|
155
|
+
const headers = {};
|
|
156
|
+
c.req.raw.headers.forEach((v, k) => (headers[k] = v));
|
|
157
|
+
const ip = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
|
|
158
|
+
instrument("request.handled", {
|
|
159
|
+
id,
|
|
160
|
+
method: c.req.method,
|
|
161
|
+
path: url.pathname,
|
|
162
|
+
status,
|
|
163
|
+
durationMs: Date.now() - start,
|
|
164
|
+
headers,
|
|
165
|
+
...(ip ? { ip } : {}),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
131
168
|
async handle(err, c) {
|
|
169
|
+
// The single choke point every error (thrown handler or unmatched route)
|
|
170
|
+
// passes through — and it runs inside the request scope, so `currentRequestId`
|
|
171
|
+
// ties the exception to its request.
|
|
172
|
+
const requestId = currentRequestId();
|
|
173
|
+
instrument("exception", {
|
|
174
|
+
error: err,
|
|
175
|
+
method: c.req.method,
|
|
176
|
+
path: new URL(c.req.url).pathname,
|
|
177
|
+
status: err instanceof HttpException ? err.status : 500,
|
|
178
|
+
...(requestId ? { requestId } : {}),
|
|
179
|
+
});
|
|
132
180
|
// Reportable exceptions: give them a chance to log/report themselves.
|
|
133
181
|
const maybe = err;
|
|
134
182
|
if (typeof maybe?.report === "function") {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Router facade. Collects route definitions declaratively; the HTTP kernel
|
|
3
3
|
* compiles them onto the underlying Hono instance at boot.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* A fluent API: named routes, per-route and group middleware, prefixes, param
|
|
6
|
+
* constraints, resource routes, and URL generation.
|
|
7
7
|
*/
|
|
8
8
|
import type { Context as HonoContext, MiddlewareHandler } from "hono";
|
|
9
9
|
import type { Container, Constructor } from "../container.js";
|
|
@@ -66,7 +66,7 @@ export declare class Route {
|
|
|
66
66
|
as(name: string): this;
|
|
67
67
|
/** Attach middleware that runs only for this route (after group middleware). */
|
|
68
68
|
middleware(mw: MiddlewareRef | MiddlewareRef[]): this;
|
|
69
|
-
/** Alias for middleware()
|
|
69
|
+
/** Alias for middleware(). */
|
|
70
70
|
use(mw: MiddlewareRef | MiddlewareRef[]): this;
|
|
71
71
|
/** Constrain a route parameter with a regex, source string, or matcher. */
|
|
72
72
|
where(param: string, matcher: Matcher): this;
|
|
@@ -81,7 +81,7 @@ export declare class RouteGroup {
|
|
|
81
81
|
constructor(routes: RouteDefinition[]);
|
|
82
82
|
prefix(prefix: string): this;
|
|
83
83
|
middleware(mw: MiddlewareRef | MiddlewareRef[]): this;
|
|
84
|
-
/** Alias for middleware()
|
|
84
|
+
/** Alias for middleware(). */
|
|
85
85
|
use(mw: MiddlewareRef | MiddlewareRef[]): this;
|
|
86
86
|
/** Attach metadata to every route in the group (a route's own config wins). */
|
|
87
87
|
config(data: Record<string, unknown>): this;
|
|
@@ -115,7 +115,7 @@ declare class RouteMatcher {
|
|
|
115
115
|
constructor(router: Router, path: string);
|
|
116
116
|
/** Redirect to a path or URL. */
|
|
117
117
|
redirect(to: string, status?: number): Route;
|
|
118
|
-
/** Alias for redirect()
|
|
118
|
+
/** Alias for redirect(). */
|
|
119
119
|
redirectToPath(to: string, status?: number): Route;
|
|
120
120
|
/** Redirect to a named route, optionally with params and a query string. */
|
|
121
121
|
redirectToRoute(name: string, params?: Record<string, string | number>, options?: {
|
package/dist/core/http/router.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Router facade. Collects route definitions declaratively; the HTTP kernel
|
|
3
3
|
* compiles them onto the underlying Hono instance at boot.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* A fluent API: named routes, per-route and group middleware, prefixes, param
|
|
6
|
+
* constraints, resource routes, and URL generation.
|
|
7
7
|
*/
|
|
8
8
|
import { view, config } from "../helpers.js";
|
|
9
9
|
import { redirect as makeRedirect, request } from "../request.js";
|
|
@@ -56,7 +56,7 @@ export class Route {
|
|
|
56
56
|
this.def.middleware.push(...(Array.isArray(mw) ? mw : [mw]));
|
|
57
57
|
return this;
|
|
58
58
|
}
|
|
59
|
-
/** Alias for middleware()
|
|
59
|
+
/** Alias for middleware(). */
|
|
60
60
|
use(mw) {
|
|
61
61
|
return this.middleware(mw);
|
|
62
62
|
}
|
|
@@ -94,7 +94,7 @@ export class RouteGroup {
|
|
|
94
94
|
r.middleware.unshift(...list); // group runs first
|
|
95
95
|
return this;
|
|
96
96
|
}
|
|
97
|
-
/** Alias for middleware()
|
|
97
|
+
/** Alias for middleware(). */
|
|
98
98
|
use(mw) {
|
|
99
99
|
return this.middleware(mw);
|
|
100
100
|
}
|
|
@@ -200,7 +200,7 @@ class RouteMatcher {
|
|
|
200
200
|
redirect(to, status = 302) {
|
|
201
201
|
return this.router.get(this.path, () => makeRedirect(to, status));
|
|
202
202
|
}
|
|
203
|
-
/** Alias for redirect()
|
|
203
|
+
/** Alias for redirect(). */
|
|
204
204
|
redirectToPath(to, status = 302) {
|
|
205
205
|
return this.redirect(to, status);
|
|
206
206
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization — translations with ICU message formatting, and the
|
|
3
|
+
* `Intl` formatters that go with them.
|
|
4
|
+
*
|
|
5
|
+
* setTranslations({
|
|
6
|
+
* en: { "cart.items": "{count, plural, =0 {Your cart is empty} one {# item} other {# items}}" },
|
|
7
|
+
* fr: { "cart.items": "{count, plural, =0 {Panier vide} one {# article} other {# articles}}" },
|
|
8
|
+
* });
|
|
9
|
+
*
|
|
10
|
+
* t("cart.items", { count: 3 }); // "3 items" (the request's locale)
|
|
11
|
+
* i18n("fr").t("cart.items", { count: 3 }); // "3 articles"
|
|
12
|
+
*
|
|
13
|
+
* There is **no dependency** here, and there doesn't need to be: `Intl` ships with
|
|
14
|
+
* every modern runtime — Node and Workers both carry the full ICU data — so
|
|
15
|
+
* plurals, currencies, dates, and relative times are the platform's job. What Keel
|
|
16
|
+
* adds is the message parser on top, which is the part `Intl` doesn't do.
|
|
17
|
+
*
|
|
18
|
+
* The supported ICU subset is the part people actually use: interpolation,
|
|
19
|
+
* `plural`, `selectordinal`, `select`, `number`, `date`, and `time` — nested
|
|
20
|
+
* arbitrarily deep. See the i18n guide for the grammar.
|
|
21
|
+
*/
|
|
22
|
+
import type { MiddlewareHandler } from "hono";
|
|
23
|
+
/** A locale's messages — nested objects or flat dot-separated keys, or both. */
|
|
24
|
+
export type Translations = Record<string, unknown>;
|
|
25
|
+
/** Messages keyed by locale: `{ en: {...}, fr: {...} }`. */
|
|
26
|
+
export type TranslationsByLocale = Record<string, Translations>;
|
|
27
|
+
/** Where translations come from. Return them; Keel flattens and indexes them. */
|
|
28
|
+
export interface TranslationLoader {
|
|
29
|
+
load(): Promise<TranslationsByLocale> | TranslationsByLocale;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format an ICU message. The grammar we support:
|
|
33
|
+
*
|
|
34
|
+
* {name} interpolation
|
|
35
|
+
* {n, number} {n, number, percent} {n, number, ::currency/USD}
|
|
36
|
+
* {d, date, medium} {d, time, short}
|
|
37
|
+
* {c, plural, =0 {none} one {# item} other {# items}}
|
|
38
|
+
* {c, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}
|
|
39
|
+
* {g, select, male {He} female {She} other {They}}
|
|
40
|
+
*
|
|
41
|
+
* `#` inside a plural branch becomes the formatted number. Branches are
|
|
42
|
+
* themselves messages, so they nest. `'{'` and `'}'` are literal braces.
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatMessage(message: string, data?: Record<string, unknown>, locale?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Pick the best locale for an `Accept-Language` header.
|
|
47
|
+
*
|
|
48
|
+
* negotiateLocale("fr-CA,fr;q=0.9,en;q=0.8", ["en", "fr"], "en") // "fr"
|
|
49
|
+
*
|
|
50
|
+
* Exact matches win, then the language part (`fr-CA` → `fr`), in the client's
|
|
51
|
+
* order of preference. Falls back to `defaultLocale` when nothing matches.
|
|
52
|
+
*/
|
|
53
|
+
export declare function negotiateLocale(header: string | null | undefined, supported: string[], defaultLocale: string): string;
|
|
54
|
+
/** One locale, bound to its messages. Get one with `i18n(locale)`. */
|
|
55
|
+
export declare class I18n {
|
|
56
|
+
readonly locale: string;
|
|
57
|
+
private messages;
|
|
58
|
+
private manager;
|
|
59
|
+
constructor(locale: string, messages: Record<string, string>, manager: I18nManager);
|
|
60
|
+
/** Whether a key resolves in this locale (or its fallbacks). */
|
|
61
|
+
has(key: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Translate a key, formatting its ICU message with `data`.
|
|
64
|
+
*
|
|
65
|
+
* t("cart.items", { count: 3 }); // "3 items"
|
|
66
|
+
*
|
|
67
|
+
* A missing key doesn't throw — it goes through the configured `missing`
|
|
68
|
+
* handler (which returns the key itself by default, so the page still renders
|
|
69
|
+
* and the gap is obvious) and fires an `i18n.missing` event.
|
|
70
|
+
*/
|
|
71
|
+
t(key: string, data?: Record<string, unknown>): string;
|
|
72
|
+
formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
|
|
73
|
+
formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;
|
|
74
|
+
formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
|
|
75
|
+
formatTime(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
|
|
76
|
+
/**
|
|
77
|
+
* "3 days ago", "in 2 hours". With no unit, the best one is picked from the
|
|
78
|
+
* distance between `value` and now.
|
|
79
|
+
*/
|
|
80
|
+
formatRelativeTime(value: Date | number | string, unit?: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
|
|
81
|
+
/** "a, b, and c" — or "a, b, or c" with `{ type: "disjunction" }`. */
|
|
82
|
+
formatList(items: string[], options?: Intl.ListFormatOptions): string;
|
|
83
|
+
/** The plural category for a count: "one", "other", … */
|
|
84
|
+
formatPlural(count: number, options?: Intl.PluralRulesOptions): Intl.LDMLPluralRule;
|
|
85
|
+
/** A language/region/currency code as its display name: "fr" → "French". */
|
|
86
|
+
formatDisplayName(code: string, type?: Intl.DisplayNamesOptions["type"]): string;
|
|
87
|
+
}
|
|
88
|
+
export interface I18nOptions {
|
|
89
|
+
/** Used when nothing else matches. Default: `"en"`. */
|
|
90
|
+
defaultLocale?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Which locales this app serves. Derived from the loaded translations when
|
|
93
|
+
* omitted.
|
|
94
|
+
*/
|
|
95
|
+
supportedLocales?: string[];
|
|
96
|
+
/** Per-locale fallbacks: `{ "es-MX": "es" }`. A language prefix is tried anyway. */
|
|
97
|
+
fallbackLocales?: Record<string, string>;
|
|
98
|
+
/**
|
|
99
|
+
* What to render for a key with no translation. Default: the key itself — so
|
|
100
|
+
* the page still renders and the gap is visible rather than blank.
|
|
101
|
+
*/
|
|
102
|
+
missing?: (key: string, locale: string) => string;
|
|
103
|
+
}
|
|
104
|
+
export declare class I18nManager {
|
|
105
|
+
private translations;
|
|
106
|
+
private options;
|
|
107
|
+
constructor(options?: I18nOptions);
|
|
108
|
+
get defaultLocale(): string;
|
|
109
|
+
/** Add translations, merging into whatever is already loaded. */
|
|
110
|
+
add(data: TranslationsByLocale): this;
|
|
111
|
+
/** Load from one or more loaders. */
|
|
112
|
+
load(...loaders: TranslationLoader[]): Promise<this>;
|
|
113
|
+
/** The locales this app serves. */
|
|
114
|
+
supported(): string[];
|
|
115
|
+
/**
|
|
116
|
+
* The chain to try for a locale: itself, its configured fallback, its language
|
|
117
|
+
* prefix (`es-MX` → `es`), then the default. That's what makes a regional
|
|
118
|
+
* locale usable with only the base language translated.
|
|
119
|
+
*/
|
|
120
|
+
private chain;
|
|
121
|
+
/** An `I18n` for a locale, with its fallback chain already flattened in. */
|
|
122
|
+
locale(code?: string): I18n;
|
|
123
|
+
/** What to render for a missing key — and a chance to notice it. */
|
|
124
|
+
missing(key: string, locale: string): string;
|
|
125
|
+
}
|
|
126
|
+
/** Replace the i18n manager (options and all). */
|
|
127
|
+
export declare function setI18n(next: I18nManager): I18nManager;
|
|
128
|
+
/** The active i18n manager. */
|
|
129
|
+
export declare function getI18n(): I18nManager;
|
|
130
|
+
/** Register translations on the active manager. */
|
|
131
|
+
export declare function setTranslations(data: TranslationsByLocale): I18nManager;
|
|
132
|
+
/**
|
|
133
|
+
* An `I18n` for a locale — or, with no argument, the **current request's** locale
|
|
134
|
+
* (set by `detectLocale()`), falling back to the default outside a request.
|
|
135
|
+
*/
|
|
136
|
+
export declare function i18n(locale?: string): I18n;
|
|
137
|
+
/** Translate a key in the current request's locale. */
|
|
138
|
+
export declare function t(key: string, data?: Record<string, unknown>): string;
|
|
139
|
+
/** Translations from a plain object — the simplest loader. */
|
|
140
|
+
export declare function objectLoader(data: TranslationsByLocale): TranslationLoader;
|
|
141
|
+
export interface DetectLocaleOptions {
|
|
142
|
+
/** Read `?lang=fr` (or another param). Highest precedence when set. */
|
|
143
|
+
query?: string;
|
|
144
|
+
/** Read a cookie. Checked after the query param, before the header. */
|
|
145
|
+
cookie?: string;
|
|
146
|
+
/** Skip the `Accept-Language` header. Default: false. */
|
|
147
|
+
header?: boolean;
|
|
148
|
+
/** Your own resolver — wins over everything if it returns a locale. */
|
|
149
|
+
resolve?: (c: Parameters<MiddlewareHandler>[0]) => string | undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Work out the request's locale and stash it, so `t()` and `i18n()` pick it up
|
|
153
|
+
* anywhere in the request without threading it through.
|
|
154
|
+
*
|
|
155
|
+
* this.use(detectLocale()); // Accept-Language
|
|
156
|
+
* this.use(detectLocale({ query: "lang" })); // ?lang=fr wins
|
|
157
|
+
*
|
|
158
|
+
* Precedence: `resolve` → query → cookie → `Accept-Language` → the default. Only
|
|
159
|
+
* **supported** locales are honored, so `?lang=xx` can't put the app into a locale
|
|
160
|
+
* you have no translations for.
|
|
161
|
+
*/
|
|
162
|
+
export declare function detectLocale(options?: DetectLocaleOptions): MiddlewareHandler;
|