@better-auth/i18n 1.5.6 → 1.6.0-beta.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/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as i18n } from "./index-CLmzQKXC.mjs";
1
+ import { t as i18n } from "./index-CBC5D-Ic.mjs";
2
2
 
3
3
  //#region src/client.d.ts
4
4
  /**
@@ -20,8 +20,8 @@ import { t as i18n } from "./index-CLmzQKXC.mjs";
20
20
  */
21
21
  declare const i18nClient: () => {
22
22
  id: "i18n";
23
+ version: string;
23
24
  $InferServerPlugin: ReturnType<typeof i18n>;
24
25
  };
25
26
  //#endregion
26
- export { i18nClient };
27
- //# sourceMappingURL=client.d.mts.map
27
+ export { i18nClient };
package/dist/client.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as PACKAGE_VERSION } from "./version-BqMHXjG-.mjs";
1
2
  //#region src/client.ts
2
3
  /**
3
4
  * i18n client plugin for Better Auth
@@ -19,10 +20,9 @@
19
20
  const i18nClient = () => {
20
21
  return {
21
22
  id: "i18n",
23
+ version: PACKAGE_VERSION,
22
24
  $InferServerPlugin: {}
23
25
  };
24
26
  };
25
-
26
27
  //#endregion
27
28
  export { i18nClient };
28
- //# sourceMappingURL=client.mjs.map
@@ -0,0 +1,431 @@
1
+ import { BetterAuthPluginRegistry, BetterAuthPluginRegistryIdentifier, GenericEndpointContext, UnionToIntersection } from "@better-auth/core";
2
+
3
+ //#region ../../node_modules/.pnpm/better-call@2.0.3_zod@4.3.6/node_modules/better-call/dist/cookies.d.mts
4
+ //#region src/cookies.d.ts
5
+ type CookiePrefixOptions = "host" | "secure";
6
+ type CookieOptions = {
7
+ /**
8
+ * Domain of the cookie
9
+ *
10
+ * The Domain attribute specifies which server can receive a cookie. If specified, cookies are
11
+ * available on the specified server and its subdomains. If the it is not
12
+ * specified, the cookies are available on the server that sets it but not on
13
+ * its subdomains.
14
+ *
15
+ * @example
16
+ * `domain: "example.com"`
17
+ */
18
+ domain?: string;
19
+ /**
20
+ * A lifetime of a cookie. Permanent cookies are deleted after the date specified in the
21
+ * Expires attribute:
22
+ *
23
+ * Expires has been available for longer than Max-Age, however Max-Age is less error-prone, and
24
+ * takes precedence when both are set. The rationale behind this is that when you set an
25
+ * Expires date and time, they're relative to the client the cookie is being set on. If the
26
+ * server is set to a different time, this could cause errors
27
+ */
28
+ expires?: Date;
29
+ /**
30
+ * Forbids JavaScript from accessing the cookie, for example, through the Document.cookie
31
+ * property. Note that a cookie that has been created with HttpOnly will still be sent with
32
+ * JavaScript-initiated requests, for example, when calling XMLHttpRequest.send() or fetch().
33
+ * This mitigates attacks against cross-site scripting
34
+ */
35
+ httpOnly?: boolean;
36
+ /**
37
+ * Indicates the number of seconds until the cookie expires. A zero or negative number will
38
+ * expire the cookie immediately. If both Expires and Max-Age are set, Max-Age has precedence.
39
+ *
40
+ * @example 604800 - 7 days
41
+ */
42
+ maxAge?: number;
43
+ /**
44
+ * Indicates the path that must exist in the requested URL for the browser to send the Cookie
45
+ * header.
46
+ *
47
+ * @example
48
+ * "/docs"
49
+ * // -> the request paths /docs, /docs/, /docs/Web/, and /docs/Web/HTTP will all match. the request paths /, /fr/docs will not match.
50
+ */
51
+ path?: string;
52
+ /**
53
+ * Indicates that the cookie is sent to the server only when a request is made with the https:
54
+ * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.
55
+ */
56
+ secure?: boolean;
57
+ /**
58
+ * Controls whether or not a cookie is sent with cross-site requests, providing some protection
59
+ * against cross-site request forgery attacks (CSRF).
60
+ *
61
+ * Strict - Means that the browser sends the cookie only for same-site requests, that is,
62
+ * requests originating from the same site that set the cookie. If a request originates from a
63
+ * different domain or scheme (even with the same domain), no cookies with the SameSite=Strict
64
+ * attribute are sent.
65
+ *
66
+ * Lax - Means that the cookie is not sent on cross-site requests, such as on requests to load
67
+ * images or frames, but is sent when a user is navigating to the origin site from an external
68
+ * site (for example, when following a link). This is the default behavior if the SameSite
69
+ * attribute is not specified.
70
+ *
71
+ * None - Means that the browser sends the cookie with both cross-site and same-site requests.
72
+ * The Secure attribute must also be set when setting this value.
73
+ */
74
+ sameSite?: "Strict" | "Lax" | "None" | "strict" | "lax" | "none";
75
+ /**
76
+ * Indicates that the cookie should be stored using partitioned storage. Note that if this is
77
+ * set, the Secure directive must also be set.
78
+ *
79
+ * @see https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies
80
+ */
81
+ partitioned?: boolean;
82
+ /**
83
+ * Cooke Prefix
84
+ *
85
+ * - secure: `__Secure-` -> `__Secure-cookie-name`
86
+ * - host: `__Host-` -> `__Host-cookie-name`
87
+ *
88
+ * `secure` must be set to true to use prefixes
89
+ */
90
+ prefix?: CookiePrefixOptions;
91
+ };
92
+ //#endregion
93
+ //#region ../../node_modules/.pnpm/better-call@2.0.3_zod@4.3.6/node_modules/better-call/dist/error.d.mts
94
+ declare const statusCodes: {
95
+ OK: number;
96
+ CREATED: number;
97
+ ACCEPTED: number;
98
+ NO_CONTENT: number;
99
+ MULTIPLE_CHOICES: number;
100
+ MOVED_PERMANENTLY: number;
101
+ FOUND: number;
102
+ SEE_OTHER: number;
103
+ NOT_MODIFIED: number;
104
+ TEMPORARY_REDIRECT: number;
105
+ BAD_REQUEST: number;
106
+ UNAUTHORIZED: number;
107
+ PAYMENT_REQUIRED: number;
108
+ FORBIDDEN: number;
109
+ NOT_FOUND: number;
110
+ METHOD_NOT_ALLOWED: number;
111
+ NOT_ACCEPTABLE: number;
112
+ PROXY_AUTHENTICATION_REQUIRED: number;
113
+ REQUEST_TIMEOUT: number;
114
+ CONFLICT: number;
115
+ GONE: number;
116
+ LENGTH_REQUIRED: number;
117
+ PRECONDITION_FAILED: number;
118
+ PAYLOAD_TOO_LARGE: number;
119
+ URI_TOO_LONG: number;
120
+ UNSUPPORTED_MEDIA_TYPE: number;
121
+ RANGE_NOT_SATISFIABLE: number;
122
+ EXPECTATION_FAILED: number;
123
+ "I'M_A_TEAPOT": number;
124
+ MISDIRECTED_REQUEST: number;
125
+ UNPROCESSABLE_ENTITY: number;
126
+ LOCKED: number;
127
+ FAILED_DEPENDENCY: number;
128
+ TOO_EARLY: number;
129
+ UPGRADE_REQUIRED: number;
130
+ PRECONDITION_REQUIRED: number;
131
+ TOO_MANY_REQUESTS: number;
132
+ REQUEST_HEADER_FIELDS_TOO_LARGE: number;
133
+ UNAVAILABLE_FOR_LEGAL_REASONS: number;
134
+ INTERNAL_SERVER_ERROR: number;
135
+ NOT_IMPLEMENTED: number;
136
+ BAD_GATEWAY: number;
137
+ SERVICE_UNAVAILABLE: number;
138
+ GATEWAY_TIMEOUT: number;
139
+ HTTP_VERSION_NOT_SUPPORTED: number;
140
+ VARIANT_ALSO_NEGOTIATES: number;
141
+ INSUFFICIENT_STORAGE: number;
142
+ LOOP_DETECTED: number;
143
+ NOT_EXTENDED: number;
144
+ NETWORK_AUTHENTICATION_REQUIRED: number;
145
+ };
146
+ type Status = 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511;
147
+ declare class InternalAPIError extends Error {
148
+ status: keyof typeof statusCodes | Status;
149
+ body: ({
150
+ message?: string;
151
+ code?: string;
152
+ cause?: unknown;
153
+ } & Record<string, any>) | undefined;
154
+ headers: HeadersInit;
155
+ statusCode: number;
156
+ constructor(status?: keyof typeof statusCodes | Status, body?: ({
157
+ message?: string;
158
+ code?: string;
159
+ cause?: unknown;
160
+ } & Record<string, any>) | undefined, headers?: HeadersInit, statusCode?: number);
161
+ }
162
+ type APIError = InstanceType<typeof InternalAPIError>;
163
+ declare const APIError: new (status?: Status | "OK" | "CREATED" | "ACCEPTED" | "NO_CONTENT" | "MULTIPLE_CHOICES" | "MOVED_PERMANENTLY" | "FOUND" | "SEE_OTHER" | "NOT_MODIFIED" | "TEMPORARY_REDIRECT" | "BAD_REQUEST" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_ALLOWED" | "NOT_ACCEPTABLE" | "PROXY_AUTHENTICATION_REQUIRED" | "REQUEST_TIMEOUT" | "CONFLICT" | "GONE" | "LENGTH_REQUIRED" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "URI_TOO_LONG" | "UNSUPPORTED_MEDIA_TYPE" | "RANGE_NOT_SATISFIABLE" | "EXPECTATION_FAILED" | "I'M_A_TEAPOT" | "MISDIRECTED_REQUEST" | "UNPROCESSABLE_ENTITY" | "LOCKED" | "FAILED_DEPENDENCY" | "TOO_EARLY" | "UPGRADE_REQUIRED" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "REQUEST_HEADER_FIELDS_TOO_LARGE" | "UNAVAILABLE_FOR_LEGAL_REASONS" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "HTTP_VERSION_NOT_SUPPORTED" | "VARIANT_ALSO_NEGOTIATES" | "INSUFFICIENT_STORAGE" | "LOOP_DETECTED" | "NOT_EXTENDED" | "NETWORK_AUTHENTICATION_REQUIRED" | undefined, body?: ({
164
+ message?: string;
165
+ code?: string;
166
+ cause?: unknown;
167
+ } & Record<string, any>) | undefined, headers?: HeadersInit | undefined, statusCode?: number | undefined) => InternalAPIError & {
168
+ errorStack: string | undefined;
169
+ }; //#endregion
170
+ //#endregion
171
+ //#region ../../node_modules/.pnpm/better-call@2.0.3_zod@4.3.6/node_modules/better-call/dist/helper.d.mts
172
+ type Prettify<T> = 0 extends 1 & T ? any : { [K in keyof T]: T[K] } & {};
173
+ //#endregion
174
+ //#region ../../node_modules/.pnpm/better-call@2.0.3_zod@4.3.6/node_modules/better-call/dist/middleware.d.mts
175
+ //#region src/middleware.d.ts
176
+ type MiddlewareContext<Context = {}> = {
177
+ /**
178
+ * Method
179
+ *
180
+ * The request method
181
+ */
182
+ method: string;
183
+ /**
184
+ * Path
185
+ *
186
+ * The path of the endpoint
187
+ */
188
+ path: string;
189
+ /**
190
+ * Body
191
+ *
192
+ * The body object will be the parsed JSON from the request and validated
193
+ * against the body schema if it exists
194
+ */
195
+ body: any;
196
+ /**
197
+ * Query
198
+ *
199
+ * The query object will be the parsed query string from the request
200
+ * and validated against the query schema if it exists
201
+ */
202
+ query: Record<string, any> | undefined;
203
+ /**
204
+ * Params
205
+ *
206
+ * If the path is `/user/:id` and the request is `/user/1` then the
207
+ * params will be `{ id: "1" }` and if the path includes a wildcard like
208
+ * `/user/*` then the params will be `{ _: "1" }` where `_` is the wildcard
209
+ * key. If the wildcard is named like `/user/**:name` then the params will
210
+ * be `{ name: string }`
211
+ */
212
+ params: Record<string, any> | undefined;
213
+ /**
214
+ * Request object
215
+ *
216
+ * If `requireRequest` is set to true in the endpoint options this will be
217
+ * required
218
+ */
219
+ request: Request | undefined;
220
+ /**
221
+ * Headers
222
+ *
223
+ * If `requireHeaders` is set to true in the endpoint options this will be
224
+ * required
225
+ */
226
+ headers: Headers | undefined;
227
+ /**
228
+ * Set header
229
+ *
230
+ * If it's called outside of a request it will just be ignored.
231
+ */
232
+ setHeader: (key: string, value: string) => void;
233
+ /**
234
+ * Set the response status code
235
+ */
236
+ setStatus: (status: Status) => void;
237
+ /**
238
+ * Get header
239
+ *
240
+ * If it's called outside of a request it will just return null
241
+ *
242
+ * @param key - The key of the header
243
+ */
244
+ getHeader: (key: string) => string | null;
245
+ /**
246
+ * Get a cookie value from the request
247
+ *
248
+ * @param key - The key of the cookie
249
+ * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`
250
+ * @returns The value of the cookie
251
+ */
252
+ getCookie: (key: string, prefix?: CookiePrefixOptions) => string | null;
253
+ /**
254
+ * Get a signed cookie value from the request
255
+ *
256
+ * @param key - The key of the cookie
257
+ * @param secret - The secret of the signed cookie
258
+ * @param prefix - The prefix of the cookie between `__Secure-` and `__Host-`
259
+ * @returns The value of the cookie or null if the cookie is not found or false if the signature is invalid
260
+ */
261
+ getSignedCookie: (key: string, secret: string, prefix?: CookiePrefixOptions) => Promise<string | null | false>;
262
+ /**
263
+ * Set a cookie value in the response
264
+ *
265
+ * @param key - The key of the cookie
266
+ * @param value - The value to set
267
+ * @param options - The options of the cookie
268
+ * @returns The cookie string
269
+ */
270
+ setCookie: (key: string, value: string, options?: CookieOptions) => string;
271
+ /**
272
+ * Set signed cookie
273
+ *
274
+ * @param key - The key of the cookie
275
+ * @param value - The value to set
276
+ * @param secret - The secret to sign the cookie with
277
+ * @param options - The options of the cookie
278
+ * @returns The cookie string
279
+ */
280
+ setSignedCookie: (key: string, value: string, secret: string, options?: CookieOptions) => Promise<string>;
281
+ /**
282
+ * JSON
283
+ *
284
+ * A helper function to create a JSON response with the correct headers
285
+ * and status code. If `asResponse` is set to true in the context then
286
+ * it will return a Response object instead of the JSON object.
287
+ *
288
+ * @param json - The JSON object to return
289
+ * @param routerResponse - The response object to return if `asResponse` is
290
+ * true in the context this will take precedence
291
+ */
292
+ json: <R extends Record<string, any> | null>(json: R, routerResponse?: {
293
+ status?: number;
294
+ headers?: Record<string, string>;
295
+ response?: Response;
296
+ body?: Record<string, any>;
297
+ } | Response) => R;
298
+ /**
299
+ * Middleware context
300
+ */
301
+ context: Prettify<Context>;
302
+ /**
303
+ * Redirect to a new URL
304
+ */
305
+ redirect: (url: string) => APIError;
306
+ /**
307
+ * Return error
308
+ */
309
+ error: (status: keyof typeof statusCodes | Status, body?: {
310
+ message?: string;
311
+ code?: string;
312
+ } & Record<string, any>, headers?: HeadersInit) => APIError;
313
+ asResponse?: boolean;
314
+ returnHeaders?: boolean;
315
+ returnStatus?: boolean;
316
+ responseHeaders: Headers;
317
+ };
318
+ type DefaultHandler = (inputCtx: MiddlewareContext<any>) => Promise<any>;
319
+ type Middleware<Handler extends (inputCtx: MiddlewareContext<any>) => Promise<any> = DefaultHandler> = Handler & {
320
+ options: Record<string, any>;
321
+ };
322
+ //#endregion
323
+ //#region src/types.d.ts
324
+ type ALL_PLUGIN_ERROR_CODE_KEYS = keyof UnionToIntersection<{ [Key in Exclude<BetterAuthPluginRegistryIdentifier, "i18n">]: BetterAuthPluginRegistry<unknown, unknown>[Key] extends {
325
+ creator: infer C;
326
+ } ? C extends ((...args: any[]) => infer P) ? P extends {
327
+ $ERROR_CODES: infer E;
328
+ } ? E : {} : {} : {} }[Exclude<BetterAuthPluginRegistryIdentifier, "i18n">]>;
329
+ type InternalTranslationDictionary = Partial<{ [Key in ALL_PLUGIN_ERROR_CODE_KEYS]: string }>;
330
+ /**
331
+ * Translation dictionary mapping error codes to translated messages
332
+ */
333
+ type TranslationDictionary = InternalTranslationDictionary & Record<string, string>;
334
+ /**
335
+ * Locale detection strategy
336
+ */
337
+ type LocaleDetectionStrategy = "header" | "cookie" | "session" | "callback";
338
+ /**
339
+ * Options for the i18n plugin
340
+ */
341
+ interface I18nOptions<Locales extends string[]> {
342
+ /**
343
+ * Translation dictionaries keyed by locale code
344
+ * @example
345
+ * {
346
+ * en: { USER_NOT_FOUND: "User not found" },
347
+ * fr: { USER_NOT_FOUND: "Utilisateur non trouvé" }
348
+ * }
349
+ */
350
+ translations: { [Locale in Locales[number]]: TranslationDictionary };
351
+ /**
352
+ * Default/fallback locale when detection fails
353
+ * @default "en"
354
+ */
355
+ defaultLocale?: Locales[number] | undefined;
356
+ /**
357
+ * Locale detection strategies in priority order
358
+ * @default ["header"]
359
+ */
360
+ detection?: LocaleDetectionStrategy[] | undefined;
361
+ /**
362
+ * Cookie name for locale detection (when "cookie" strategy is used)
363
+ * @default "locale"
364
+ */
365
+ localeCookie?: string | undefined;
366
+ /**
367
+ * User field name for stored locale preference (when "session" strategy is used)
368
+ * @default "locale"
369
+ */
370
+ userLocaleField?: string | undefined;
371
+ /**
372
+ * Custom locale detection function (when "callback" strategy is used).
373
+ * @example
374
+ * getLocale: (ctx) => {
375
+ * return ctx.headers?.get("X-Custom-Locale") ?? null;
376
+ * }
377
+ */
378
+ getLocale?: undefined | ((ctx: GenericEndpointContext) => Promise<Locales[number] | null> | Locales[number] | null);
379
+ }
380
+ //#endregion
381
+ //#region src/index.d.ts
382
+ declare module "@better-auth/core" {
383
+ interface BetterAuthPluginRegistry<AuthOptions, Options> {
384
+ i18n: {
385
+ creator: typeof i18n;
386
+ };
387
+ }
388
+ }
389
+ /**
390
+ * i18n plugin for Better Auth
391
+ *
392
+ * Translates error messages based on detected locale.
393
+ *
394
+ * @example
395
+ * ```ts
396
+ * import { betterAuth } from "better-auth";
397
+ * import { i18n } from "@better-auth/i18n";
398
+ *
399
+ * export const auth = betterAuth({
400
+ * plugins: [
401
+ * i18n({
402
+ * translations: {
403
+ * en: { USER_NOT_FOUND: "User not found" },
404
+ * fr: { USER_NOT_FOUND: "Utilisateur non trouvé" },
405
+ * },
406
+ * detection: ["header", "cookie"],
407
+ * }),
408
+ * ],
409
+ * });
410
+ * ```
411
+ */
412
+ declare const i18n: <Locales extends string[]>(options: I18nOptions<Locales>) => {
413
+ id: "i18n";
414
+ version: string;
415
+ hooks: {
416
+ after: {
417
+ matcher: () => true;
418
+ handler: Middleware<(inputContext: Record<string, any>) => Promise<void>>;
419
+ }[];
420
+ };
421
+ options: {
422
+ translations: { [Locale in Locales[number]]: TranslationDictionary };
423
+ defaultLocale: Locales[number];
424
+ detection: LocaleDetectionStrategy[];
425
+ localeCookie: string;
426
+ userLocaleField: string;
427
+ getLocale?: ((ctx: GenericEndpointContext) => Locales[number] | Promise<Locales[number] | null> | null) | undefined;
428
+ };
429
+ };
430
+ //#endregion
431
+ export { TranslationDictionary as i, I18nOptions as n, LocaleDetectionStrategy as r, i18n as t };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as TranslationDictionary, n as I18nOptions, r as LocaleDetectionStrategy, t as i18n } from "./index-CLmzQKXC.mjs";
1
+ import { i as TranslationDictionary, n as I18nOptions, r as LocaleDetectionStrategy, t as i18n } from "./index-CBC5D-Ic.mjs";
2
2
  export { I18nOptions, LocaleDetectionStrategy, TranslationDictionary, i18n };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
+ import { t as PACKAGE_VERSION } from "./version-BqMHXjG-.mjs";
1
2
  import { APIError, createAuthMiddleware, isAPIError } from "better-auth/api";
2
3
  import { parseCookies } from "better-auth/cookies";
3
-
4
4
  //#region src/index.ts
5
5
  /**
6
6
  * Parse Accept-Language header and return locales sorted by quality
@@ -87,6 +87,7 @@ const i18n = (options) => {
87
87
  }
88
88
  return {
89
89
  id: "i18n",
90
+ version: PACKAGE_VERSION,
90
91
  hooks: { after: [{
91
92
  matcher: () => true,
92
93
  handler: createAuthMiddleware(async (ctx) => {
@@ -107,7 +108,5 @@ const i18n = (options) => {
107
108
  options: opts
108
109
  };
109
110
  };
110
-
111
111
  //#endregion
112
112
  export { i18n };
113
- //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,5 @@
1
+ //#endregion
2
+ //#region src/version.ts
3
+ const PACKAGE_VERSION = "1.6.0-beta.0";
4
+ //#endregion
5
+ export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/i18n",
3
- "version": "1.5.6",
3
+ "version": "1.6.0-beta.0",
4
4
  "description": "i18n plugin for Better Auth - translate error messages",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  "publishConfig": {
21
21
  "access": "public"
22
22
  },
23
+ "sideEffects": false,
23
24
  "files": [
24
25
  "dist"
25
26
  ],
@@ -49,18 +50,18 @@
49
50
  }
50
51
  },
51
52
  "devDependencies": {
52
- "tsdown": "0.21.0-beta.2",
53
- "@better-auth/core": "1.5.6",
54
- "better-auth": "1.5.6"
53
+ "tsdown": "0.21.1",
54
+ "@better-auth/core": "1.6.0-beta.0",
55
+ "better-auth": "1.6.0-beta.0"
55
56
  },
56
57
  "peerDependencies": {
57
- "@better-auth/core": "1.5.6",
58
- "better-auth": "1.5.6"
58
+ "@better-auth/core": "^1.6.0-beta.0",
59
+ "better-auth": "^1.6.0-beta.0"
59
60
  },
60
61
  "scripts": {
61
62
  "build": "tsdown",
62
63
  "dev": "tsdown --watch",
63
- "lint:package": "publint run --strict",
64
+ "lint:package": "publint run --strict --pack false",
64
65
  "lint:types": "attw --profile esm-only --pack .",
65
66
  "typecheck": "tsc --project tsconfig.json",
66
67
  "test": "vitest",
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type { BetterAuthClientPlugin } from \"@better-auth/core\";\nimport type { i18n } from \".\";\n\n/**\n * i18n client plugin for Better Auth\n *\n * This client plugin provides type inference for the i18n server plugin.\n * Error messages from the server will already be translated based on\n * the detected locale.\n *\n * @example\n * ```ts\n * import { createAuthClient } from \"better-auth/client\";\n * import { i18nClient } from \"@better-auth/i18n/client\";\n *\n * export const client = createAuthClient({\n * plugins: [i18nClient()],\n * });\n * ```\n */\nexport const i18nClient = () => {\n\treturn {\n\t\tid: \"i18n\",\n\t\t$InferServerPlugin: {} as ReturnType<typeof i18n>,\n\t} satisfies BetterAuthClientPlugin;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,MAAa,mBAAmB;AAC/B,QAAO;EACN,IAAI;EACJ,oBAAoB,EAAE;EACtB"}
@@ -1,112 +0,0 @@
1
- import * as better_call0 from "better-call";
2
- import { BetterAuthPluginRegistry, BetterAuthPluginRegistryIdentifier, GenericEndpointContext, UnionToIntersection } from "@better-auth/core";
3
-
4
- //#region src/types.d.ts
5
- type ALL_PLUGIN_ERROR_CODE_KEYS = keyof UnionToIntersection<{ [Key in Exclude<BetterAuthPluginRegistryIdentifier, "i18n">]: BetterAuthPluginRegistry<unknown, unknown>[Key] extends {
6
- creator: infer C;
7
- } ? C extends ((...args: any[]) => infer P) ? P extends {
8
- $ERROR_CODES: infer E;
9
- } ? E : {} : {} : {} }[Exclude<BetterAuthPluginRegistryIdentifier, "i18n">]>;
10
- type InternalTranslationDictionary = Partial<{ [Key in ALL_PLUGIN_ERROR_CODE_KEYS]: string }>;
11
- /**
12
- * Translation dictionary mapping error codes to translated messages
13
- */
14
- type TranslationDictionary = InternalTranslationDictionary & Record<string, string>;
15
- /**
16
- * Locale detection strategy
17
- */
18
- type LocaleDetectionStrategy = "header" | "cookie" | "session" | "callback";
19
- /**
20
- * Options for the i18n plugin
21
- */
22
- interface I18nOptions<Locales extends string[]> {
23
- /**
24
- * Translation dictionaries keyed by locale code
25
- * @example
26
- * {
27
- * en: { USER_NOT_FOUND: "User not found" },
28
- * fr: { USER_NOT_FOUND: "Utilisateur non trouvé" }
29
- * }
30
- */
31
- translations: { [Locale in Locales[number]]: TranslationDictionary };
32
- /**
33
- * Default/fallback locale when detection fails
34
- * @default "en"
35
- */
36
- defaultLocale?: Locales[number] | undefined;
37
- /**
38
- * Locale detection strategies in priority order
39
- * @default ["header"]
40
- */
41
- detection?: LocaleDetectionStrategy[] | undefined;
42
- /**
43
- * Cookie name for locale detection (when "cookie" strategy is used)
44
- * @default "locale"
45
- */
46
- localeCookie?: string | undefined;
47
- /**
48
- * User field name for stored locale preference (when "session" strategy is used)
49
- * @default "locale"
50
- */
51
- userLocaleField?: string | undefined;
52
- /**
53
- * Custom locale detection function (when "callback" strategy is used).
54
- * @example
55
- * getLocale: (ctx) => {
56
- * return ctx.headers?.get("X-Custom-Locale") ?? null;
57
- * }
58
- */
59
- getLocale?: undefined | ((ctx: GenericEndpointContext) => Promise<Locales[number] | null> | Locales[number] | null);
60
- }
61
- //#endregion
62
- //#region src/index.d.ts
63
- declare module "@better-auth/core" {
64
- interface BetterAuthPluginRegistry<AuthOptions, Options> {
65
- i18n: {
66
- creator: typeof i18n;
67
- };
68
- }
69
- }
70
- /**
71
- * i18n plugin for Better Auth
72
- *
73
- * Translates error messages based on detected locale.
74
- *
75
- * @example
76
- * ```ts
77
- * import { betterAuth } from "better-auth";
78
- * import { i18n } from "@better-auth/i18n";
79
- *
80
- * export const auth = betterAuth({
81
- * plugins: [
82
- * i18n({
83
- * translations: {
84
- * en: { USER_NOT_FOUND: "User not found" },
85
- * fr: { USER_NOT_FOUND: "Utilisateur non trouvé" },
86
- * },
87
- * detection: ["header", "cookie"],
88
- * }),
89
- * ],
90
- * });
91
- * ```
92
- */
93
- declare const i18n: <Locales extends string[]>(options: I18nOptions<Locales>) => {
94
- id: "i18n";
95
- hooks: {
96
- after: {
97
- matcher: () => true;
98
- handler: (inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>;
99
- }[];
100
- };
101
- options: {
102
- translations: { [Locale in Locales[number]]: TranslationDictionary };
103
- defaultLocale: Locales[number];
104
- detection: LocaleDetectionStrategy[];
105
- localeCookie: string;
106
- userLocaleField: string;
107
- getLocale?: ((ctx: GenericEndpointContext) => Locales[number] | Promise<Locales[number] | null> | null) | undefined;
108
- };
109
- };
110
- //#endregion
111
- export { TranslationDictionary as i, I18nOptions as n, LocaleDetectionStrategy as r, i18n as t };
112
- //# sourceMappingURL=index-CLmzQKXC.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBetterAuthPlugin,\n\tGenericEndpointContext,\n} from \"@better-auth/core\";\nimport { APIError, createAuthMiddleware, isAPIError } from \"better-auth/api\";\nimport { parseCookies } from \"better-auth/cookies\";\nimport type { I18nOptions, LocaleDetectionStrategy } from \"./types\";\n\nexport type {\n\tI18nOptions,\n\tLocaleDetectionStrategy,\n\tTranslationDictionary,\n} from \"./types\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\ti18n: {\n\t\t\tcreator: typeof i18n;\n\t\t};\n\t}\n}\n\n/**\n * Parse Accept-Language header and return locales sorted by quality\n */\nfunction parseAcceptLanguage(header: string | null): string[] {\n\tif (!header) return [];\n\treturn header\n\t\t.split(\",\")\n\t\t.map((part) => {\n\t\t\tconst [localeStr, quality = \"q=1\"] = part.trim().split(\";\");\n\t\t\tconst q = Number.parseFloat(quality.replace(\"q=\", \"\"));\n\t\t\t// Get base locale (e.g., \"en\" from \"en-US\")\n\t\t\tconst locale = localeStr?.trim().split(\"-\")[0] ?? \"\";\n\t\t\treturn { locale, q };\n\t\t})\n\t\t.filter((item) => item.locale.length > 0)\n\t\t.sort((a, b) => b.q - a.q)\n\t\t.map((item) => item.locale);\n}\n\n/**\n * i18n plugin for Better Auth\n *\n * Translates error messages based on detected locale.\n *\n * @example\n * ```ts\n * import { betterAuth } from \"better-auth\";\n * import { i18n } from \"@better-auth/i18n\";\n *\n * export const auth = betterAuth({\n * plugins: [\n * i18n({\n * translations: {\n * en: { USER_NOT_FOUND: \"User not found\" },\n * fr: { USER_NOT_FOUND: \"Utilisateur non trouvé\" },\n * },\n * detection: [\"header\", \"cookie\"],\n * }),\n * ],\n * });\n * ```\n */\nexport const i18n = <Locales extends string[]>(\n\toptions: I18nOptions<Locales>,\n) => {\n\tconst availableLocales = Object.keys(options.translations);\n\n\tlet defaultLocale: Locales[number];\n\tif (\n\t\toptions.defaultLocale &&\n\t\tavailableLocales.includes(options.defaultLocale)\n\t) {\n\t\tdefaultLocale = options.defaultLocale;\n\t} else if (availableLocales.includes(\"en\")) {\n\t\tdefaultLocale = \"en\" as Locales[number];\n\t} else if (availableLocales.length > 0) {\n\t\tdefaultLocale = availableLocales[0] as Locales[number];\n\t} else {\n\t\tthrow new Error(\n\t\t\t\"i18n plugin: translations object is empty. At least one locale must be provided.\",\n\t\t);\n\t}\n\n\tconst opts = {\n\t\tdefaultLocale,\n\t\tdetection: [\"header\"] as LocaleDetectionStrategy[],\n\t\tlocaleCookie: \"locale\",\n\t\tuserLocaleField: \"locale\",\n\t\t...options,\n\t};\n\n\tasync function detectLocale(\n\t\tctx: GenericEndpointContext,\n\t): Promise<Locales[number]> {\n\t\tfor (const strategy of opts.detection) {\n\t\t\tlet locale: Locales[number] | null = null;\n\n\t\t\tswitch (strategy) {\n\t\t\t\tcase \"header\": {\n\t\t\t\t\tconst acceptLang = ctx.headers?.get(\"Accept-Language\") ?? null;\n\t\t\t\t\tconst preferred = parseAcceptLanguage(acceptLang);\n\t\t\t\t\tlocale = preferred.find((l) => availableLocales.includes(l)) ?? null;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"cookie\": {\n\t\t\t\t\tconst cookieHeader = ctx.headers?.get(\"Cookie\");\n\t\t\t\t\tif (cookieHeader) {\n\t\t\t\t\t\tconst cookies = parseCookies(cookieHeader);\n\t\t\t\t\t\tconst cookieLocale = cookies.get(opts.localeCookie);\n\t\t\t\t\t\tif (cookieLocale && availableLocales.includes(cookieLocale)) {\n\t\t\t\t\t\t\tlocale = cookieLocale;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"session\": {\n\t\t\t\t\tif (ctx.context.session?.user) {\n\t\t\t\t\t\tconst userLocale = (\n\t\t\t\t\t\t\tctx.context.session.user as Record<string, unknown>\n\t\t\t\t\t\t)[opts.userLocaleField];\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof userLocale === \"string\" &&\n\t\t\t\t\t\t\tavailableLocales.includes(userLocale)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tlocale = userLocale;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"callback\": {\n\t\t\t\t\tif (opts.getLocale) {\n\t\t\t\t\t\tconst callbackLocale = await opts.getLocale(ctx);\n\t\t\t\t\t\tif (callbackLocale && availableLocales.includes(callbackLocale)) {\n\t\t\t\t\t\t\tlocale = callbackLocale;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (locale) return locale;\n\t\t}\n\n\t\treturn opts.defaultLocale;\n\t}\n\n\treturn {\n\t\tid: \"i18n\",\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher: () => true,\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst returned = ctx.context.returned;\n\n\t\t\t\t\t\tif (!isAPIError(returned)) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst errorCode = (returned.body as Record<string, unknown>)?.code;\n\t\t\t\t\t\tif (typeof errorCode !== \"string\") {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst locale = await detectLocale(ctx);\n\n\t\t\t\t\t\tconst translation = opts.translations[locale]?.[errorCode];\n\n\t\t\t\t\t\tif (!translation) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthrow new APIError(returned.status, {\n\t\t\t\t\t\t\tcode: errorCode,\n\t\t\t\t\t\t\tmessage: translation,\n\t\t\t\t\t\t\toriginalMessage: returned.message,\n\t\t\t\t\t\t});\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\n\t\toptions: opts,\n\t} satisfies BetterAuthPlugin;\n};\n\nexport type * from \"./types\";\n"],"mappings":";;;;;;;AAyBA,SAAS,oBAAoB,QAAiC;AAC7D,KAAI,CAAC,OAAQ,QAAO,EAAE;AACtB,QAAO,OACL,MAAM,IAAI,CACV,KAAK,SAAS;EACd,MAAM,CAAC,WAAW,UAAU,SAAS,KAAK,MAAM,CAAC,MAAM,IAAI;EAC3D,MAAM,IAAI,OAAO,WAAW,QAAQ,QAAQ,MAAM,GAAG,CAAC;AAGtD,SAAO;GAAE,QADM,WAAW,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM;GACjC;GAAG;GACnB,CACD,QAAQ,SAAS,KAAK,OAAO,SAAS,EAAE,CACxC,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,EAAE,CACzB,KAAK,SAAS,KAAK,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;AA0B7B,MAAa,QACZ,YACI;CACJ,MAAM,mBAAmB,OAAO,KAAK,QAAQ,aAAa;CAE1D,IAAI;AACJ,KACC,QAAQ,iBACR,iBAAiB,SAAS,QAAQ,cAAc,CAEhD,iBAAgB,QAAQ;UACd,iBAAiB,SAAS,KAAK,CACzC,iBAAgB;UACN,iBAAiB,SAAS,EACpC,iBAAgB,iBAAiB;KAEjC,OAAM,IAAI,MACT,mFACA;CAGF,MAAM,OAAO;EACZ;EACA,WAAW,CAAC,SAAS;EACrB,cAAc;EACd,iBAAiB;EACjB,GAAG;EACH;CAED,eAAe,aACd,KAC2B;AAC3B,OAAK,MAAM,YAAY,KAAK,WAAW;GACtC,IAAI,SAAiC;AAErC,WAAQ,UAAR;IACC,KAAK;AAGJ,cADkB,oBADC,IAAI,SAAS,IAAI,kBAAkB,IAAI,KACT,CAC9B,MAAM,MAAM,iBAAiB,SAAS,EAAE,CAAC,IAAI;AAChE;IAGD,KAAK,UAAU;KACd,MAAM,eAAe,IAAI,SAAS,IAAI,SAAS;AAC/C,SAAI,cAAc;MAEjB,MAAM,eADU,aAAa,aAAa,CACb,IAAI,KAAK,aAAa;AACnD,UAAI,gBAAgB,iBAAiB,SAAS,aAAa,CAC1D,UAAS;;AAGX;;IAGD,KAAK;AACJ,SAAI,IAAI,QAAQ,SAAS,MAAM;MAC9B,MAAM,aACL,IAAI,QAAQ,QAAQ,KACnB,KAAK;AACP,UACC,OAAO,eAAe,YACtB,iBAAiB,SAAS,WAAW,CAErC,UAAS;;AAGX;IAGD,KAAK;AACJ,SAAI,KAAK,WAAW;MACnB,MAAM,iBAAiB,MAAM,KAAK,UAAU,IAAI;AAChD,UAAI,kBAAkB,iBAAiB,SAAS,eAAe,CAC9D,UAAS;;AAGX;;AAIF,OAAI,OAAQ,QAAO;;AAGpB,SAAO,KAAK;;AAGb,QAAO;EACN,IAAI;EACJ,OAAO,EACN,OAAO,CACN;GACC,eAAe;GACf,SAAS,qBAAqB,OAAO,QAAQ;IAC5C,MAAM,WAAW,IAAI,QAAQ;AAE7B,QAAI,CAAC,WAAW,SAAS,CACxB;IAGD,MAAM,YAAa,SAAS,MAAkC;AAC9D,QAAI,OAAO,cAAc,SACxB;IAGD,MAAM,SAAS,MAAM,aAAa,IAAI;IAEtC,MAAM,cAAc,KAAK,aAAa,UAAU;AAEhD,QAAI,CAAC,YACJ;AAGD,UAAM,IAAI,SAAS,SAAS,QAAQ;KACnC,MAAM;KACN,SAAS;KACT,iBAAiB,SAAS;KAC1B,CAAC;KACD;GACF,CACD,EACD;EAED,SAAS;EACT"}