@bgord/bun 0.1.0 → 0.2.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/api-key-shield.d.ts +1 -4
- package/dist/api-version.d.ts +1 -4
- package/dist/auth-shield.d.ts +29 -32
- package/dist/bgord-bun.cjs +1 -1
- package/dist/bgord-bun.cjs.map +1 -1
- package/dist/bgord-bun.modern.js +1 -1
- package/dist/bgord-bun.modern.js.map +1 -1
- package/dist/bgord-bun.module.js +1 -1
- package/dist/bgord-bun.module.js.map +1 -1
- package/dist/bgord-bun.umd.js +1 -1
- package/dist/bgord-bun.umd.js.map +1 -1
- package/dist/cache-response.d.ts +12 -15
- package/dist/cache-static-files.d.ts +8 -9
- package/dist/context.d.ts +1 -4
- package/dist/download-file.d.ts +12 -10
- package/dist/etag-extractor.d.ts +2 -4
- package/dist/file-uploader.d.ts +12 -12
- package/dist/healthcheck.d.ts +1 -1
- package/dist/http-logger.d.ts +1 -4
- package/dist/i18n.d.ts +26 -31
- package/dist/image-processor.d.ts +4 -0
- package/dist/index.d.ts +11 -4
- package/dist/rate-limit-shield.d.ts +1 -4
- package/dist/time-zone-offset.d.ts +1 -4
- package/package.json +6 -5
- package/src/auth-shield.ts +152 -0
- package/src/cache-response.ts +35 -0
- package/src/cache-static-files.ts +34 -0
- package/src/download-file.ts +15 -0
- package/src/file-uploader.ts +48 -0
- package/src/http-logger.ts +6 -6
- package/src/i18n.ts +110 -0
- package/src/image-processor.ts +5 -0
- package/src/index.ts +11 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./api-key-shield";
|
|
2
2
|
export * from "./api-version";
|
|
3
|
-
export * from "./
|
|
3
|
+
export * from "./auth-shield";
|
|
4
4
|
export * from "./context";
|
|
5
5
|
export * from "./etag-extractor";
|
|
6
|
+
export * from "./graceful-shutdown";
|
|
7
|
+
export * from "./healthcheck";
|
|
6
8
|
export * from "./http-logger";
|
|
7
|
-
export * from "./api-key-shield";
|
|
8
9
|
export * from "./rate-limit-shield";
|
|
9
|
-
export * from "./
|
|
10
|
+
export * from "./time-zone-offset";
|
|
11
|
+
export * from "./cache-static-files";
|
|
12
|
+
export * from "./cache-response";
|
|
13
|
+
export * from "./download-file";
|
|
14
|
+
export * from "./i18n";
|
|
15
|
+
export * from "./file-uploader";
|
|
16
|
+
export * from "./image-processor";
|
|
@@ -4,8 +4,5 @@ declare type RateLimitShieldOptionsType = {
|
|
|
4
4
|
ms: bg.Schema.TimestampType;
|
|
5
5
|
};
|
|
6
6
|
export declare const TooManyRequestsError: HTTPException;
|
|
7
|
-
export declare const rateLimitShield: (options: RateLimitShieldOptionsType) => import("hono
|
|
8
|
-
Bindings: any;
|
|
9
|
-
Variables: any;
|
|
10
|
-
}, string, {}>;
|
|
7
|
+
export declare const rateLimitShield: (options: RateLimitShieldOptionsType) => import("hono").MiddlewareHandler<any, string, {}>;
|
|
11
8
|
export {};
|
|
@@ -8,10 +8,7 @@ export declare type TimeZoneOffsetVariables = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare class TimeZoneOffset {
|
|
10
10
|
static TIME_ZONE_OFFSET_HEADER_NAME: string;
|
|
11
|
-
static attach: import("hono
|
|
12
|
-
Bindings: any;
|
|
13
|
-
Variables: any;
|
|
14
|
-
}, string, {}>;
|
|
11
|
+
static attach: import("hono").MiddlewareHandler<any, string, {}>;
|
|
15
12
|
static adjustTimestamp(timestamp: bg.Schema.TimestampType, timeZoneOffsetMs: bg.Schema.TimeZoneOffsetValueType): bg.Schema.TimestampType;
|
|
16
13
|
static adjustDate(timestamp: bg.Schema.TimestampType, timeZoneOffsetMs: bg.Schema.TimeZoneOffsetValueType): Date;
|
|
17
14
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.2.0",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@bgord/bun",
|
|
5
5
|
"type": "module",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"@commitlint/config-conventional": "19.5.0",
|
|
29
29
|
"@types/bun": "1.1.11",
|
|
30
30
|
"@types/lodash": "4.17.10",
|
|
31
|
-
"cspell": "8.
|
|
31
|
+
"cspell": "8.15.3",
|
|
32
32
|
"husky": "9.1.6",
|
|
33
|
-
"knip": "5.33.
|
|
33
|
+
"knip": "5.33.3",
|
|
34
34
|
"microbundle": "0.15.1",
|
|
35
35
|
"only-allow": "1.2.1",
|
|
36
36
|
"shellcheck": "3.0.0",
|
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@bgord/node": "0.82.0",
|
|
42
|
-
"hono": "4.6.
|
|
43
|
-
"lodash": "4.17.21"
|
|
42
|
+
"hono": "4.6.5",
|
|
43
|
+
"lodash": "4.17.21",
|
|
44
|
+
"sharp": "0.33.5"
|
|
44
45
|
},
|
|
45
46
|
"resolutions": {
|
|
46
47
|
"typescript": "4.7.4"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import hono from "hono";
|
|
2
|
+
import * as bgn from "@bgord/node";
|
|
3
|
+
import { Lucia } from "lucia";
|
|
4
|
+
import { createMiddleware } from "hono/factory";
|
|
5
|
+
import { HTTPException } from "hono/http-exception";
|
|
6
|
+
|
|
7
|
+
class SessionId {
|
|
8
|
+
private value: string | null;
|
|
9
|
+
|
|
10
|
+
constructor(cookie: string | undefined, lucia: Lucia) {
|
|
11
|
+
this.value = lucia.readSessionCookie(cookie ?? "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get(): SessionId["value"] {
|
|
15
|
+
return this.value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type AuthShieldConfigType<T> = {
|
|
20
|
+
Username: typeof bgn.Username;
|
|
21
|
+
Password: typeof bgn.Password;
|
|
22
|
+
HashedPassword: typeof bgn.HashedPassword;
|
|
23
|
+
lucia: Lucia;
|
|
24
|
+
findUniqueUserOrThrow: (username: bgn.Username) => Promise<T>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const AccessDeniedAuthShieldError = new HTTPException(403, {
|
|
28
|
+
message: "access_denied_auth_shield",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export class AuthShield<
|
|
32
|
+
T extends { password: bgn.PasswordType; id: bgn.IdType }
|
|
33
|
+
> {
|
|
34
|
+
private readonly config: AuthShieldConfigType<T>;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
overrides: Omit<
|
|
38
|
+
AuthShieldConfigType<T>,
|
|
39
|
+
"Username" | "Password" | "HashedPassword"
|
|
40
|
+
> & {
|
|
41
|
+
Username?: typeof bgn.Username;
|
|
42
|
+
Password?: typeof bgn.Password;
|
|
43
|
+
HashedPassword?: typeof bgn.HashedPassword;
|
|
44
|
+
}
|
|
45
|
+
) {
|
|
46
|
+
const config = {
|
|
47
|
+
Username: overrides.Username ?? bgn.Username,
|
|
48
|
+
Password: overrides.Password ?? bgn.Password,
|
|
49
|
+
HashedPassword: overrides.HashedPassword ?? bgn.HashedPassword,
|
|
50
|
+
lucia: overrides.lucia,
|
|
51
|
+
findUniqueUserOrThrow: overrides.findUniqueUserOrThrow,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
this.config = config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
verify = createMiddleware(async (c: hono.Context, next: hono.Next) => {
|
|
58
|
+
const user = c.get("user");
|
|
59
|
+
|
|
60
|
+
if (!user) {
|
|
61
|
+
throw AccessDeniedAuthShieldError;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return next();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
reverse = createMiddleware(async (c: hono.Context, next: hono.Next) => {
|
|
68
|
+
const user = c.get("user");
|
|
69
|
+
|
|
70
|
+
if (user) {
|
|
71
|
+
throw AccessDeniedAuthShieldError;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return next();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
detach = createMiddleware(async (c: hono.Context, next: hono.Next) => {
|
|
78
|
+
const cookie = c.req.header("cookie");
|
|
79
|
+
|
|
80
|
+
const sessionId = new SessionId(cookie, this.config.lucia).get();
|
|
81
|
+
|
|
82
|
+
if (!sessionId) return next();
|
|
83
|
+
|
|
84
|
+
await this.config.lucia.invalidateSession(sessionId);
|
|
85
|
+
|
|
86
|
+
return next();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
build = createMiddleware(async (c: hono.Context, next: hono.Next) => {
|
|
90
|
+
const cookie = c.req.header("cookie");
|
|
91
|
+
|
|
92
|
+
const sessionId = new SessionId(cookie, this.config.lucia).get();
|
|
93
|
+
|
|
94
|
+
if (!sessionId) {
|
|
95
|
+
c.set("user", null);
|
|
96
|
+
c.set("session", null);
|
|
97
|
+
|
|
98
|
+
return next();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { session, user } = await this.config.lucia.validateSession(
|
|
102
|
+
sessionId
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (!session) {
|
|
106
|
+
c.res.headers.set(
|
|
107
|
+
"Set-Cookie",
|
|
108
|
+
this.config.lucia.createBlankSessionCookie().serialize()
|
|
109
|
+
);
|
|
110
|
+
c.set("user", null);
|
|
111
|
+
c.set("session", null);
|
|
112
|
+
|
|
113
|
+
return next();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (session.fresh) {
|
|
117
|
+
c.res.headers.set(
|
|
118
|
+
"Set-Cookie",
|
|
119
|
+
this.config.lucia.createSessionCookie(session.id).serialize()
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
c.set("user", user);
|
|
123
|
+
c.set("session", session);
|
|
124
|
+
|
|
125
|
+
return next();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
attach = createMiddleware(async (c: hono.Context, next: hono.Next) => {
|
|
129
|
+
try {
|
|
130
|
+
const body = await c.req.raw.clone().formData();
|
|
131
|
+
|
|
132
|
+
const username = new this.config.Username(body.get("username") as string);
|
|
133
|
+
const password = new this.config.Password(body.get("password") as string);
|
|
134
|
+
|
|
135
|
+
const user = await this.config.findUniqueUserOrThrow(username);
|
|
136
|
+
|
|
137
|
+
const hashedPassword = await this.config.HashedPassword.fromHash(
|
|
138
|
+
user.password
|
|
139
|
+
);
|
|
140
|
+
await hashedPassword.matchesOrThrow(password);
|
|
141
|
+
|
|
142
|
+
const session = await this.config.lucia.createSession(user.id, {});
|
|
143
|
+
const sessionCookie = this.config.lucia.createSessionCookie(session.id);
|
|
144
|
+
|
|
145
|
+
c.res.headers.set("Set-Cookie", sessionCookie.serialize());
|
|
146
|
+
|
|
147
|
+
return next();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw AccessDeniedAuthShieldError;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createMiddleware } from "hono/factory";
|
|
2
|
+
import NodeCache from "node-cache";
|
|
3
|
+
import _ from "lodash";
|
|
4
|
+
|
|
5
|
+
export enum CacheHitEnum {
|
|
6
|
+
hit = "hit",
|
|
7
|
+
miss = "miss",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CacheResponse {
|
|
11
|
+
static readonly CACHE_HIT_HEADER = "Cache-Hit";
|
|
12
|
+
|
|
13
|
+
constructor(private readonly cache: NodeCache) {}
|
|
14
|
+
|
|
15
|
+
handle = createMiddleware(async (c, next) => {
|
|
16
|
+
const url = _.escape(c.req.url);
|
|
17
|
+
|
|
18
|
+
if (this.cache.has(url)) {
|
|
19
|
+
c.res.headers.set(CacheResponse.CACHE_HIT_HEADER, CacheHitEnum.hit);
|
|
20
|
+
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
return c.json(this.cache.get(url));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
c.res.headers.set(CacheResponse.CACHE_HIT_HEADER, CacheHitEnum.miss);
|
|
26
|
+
|
|
27
|
+
return next();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
clear = createMiddleware(async (_c, next) => {
|
|
31
|
+
this.cache.flushAll();
|
|
32
|
+
|
|
33
|
+
return next();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as bg from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
|
|
4
|
+
export enum CacheStaticFilesStrategy {
|
|
5
|
+
never = "never",
|
|
6
|
+
always = "always",
|
|
7
|
+
five_minutes = "five_minutes",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CacheStaticFiles {
|
|
11
|
+
static handle(strategy: CacheStaticFilesStrategy) {
|
|
12
|
+
return createMiddleware(async (c, next) => {
|
|
13
|
+
if (strategy === CacheStaticFilesStrategy.never) {
|
|
14
|
+
c.res.headers.set(
|
|
15
|
+
"cache-control",
|
|
16
|
+
"private, no-cache, no-store, must-revalidate"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (strategy === CacheStaticFilesStrategy.always) {
|
|
20
|
+
c.res.headers.set(
|
|
21
|
+
"cache-control",
|
|
22
|
+
`public, max-age=${bg.Time.Days(365).seconds}, immutable`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (strategy === CacheStaticFilesStrategy.five_minutes) {
|
|
26
|
+
c.res.headers.set(
|
|
27
|
+
"cache-control",
|
|
28
|
+
`public, max-age=${bg.Time.Minutes(5).seconds}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return next();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as bgn from "@bgord/node";
|
|
2
|
+
import type { PathLike } from "node:fs";
|
|
3
|
+
|
|
4
|
+
export type DownloadFileConfigType = { filename: PathLike; mime: bgn.Mime };
|
|
5
|
+
|
|
6
|
+
export class DownloadFile {
|
|
7
|
+
static attach(config: DownloadFileConfigType) {
|
|
8
|
+
return {
|
|
9
|
+
headers: new Headers({
|
|
10
|
+
"Content-Disposition": `attachment; filename="${config.filename}"`,
|
|
11
|
+
"Content-Type": config.mime.raw,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as bgn from "@bgord/node";
|
|
2
|
+
import { createMiddleware } from "hono/factory";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
import { bodyLimit } from "hono/body-limit";
|
|
5
|
+
|
|
6
|
+
export const InvalidFileMimeTypeError = new HTTPException(400, {
|
|
7
|
+
message: "invalid_file_mime_type_error",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const FileTooBigError = new HTTPException(400, {
|
|
11
|
+
message: "file_too_big_error",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
type FileUploaderConfigType = {
|
|
15
|
+
mimeTypes: string[];
|
|
16
|
+
maxFilesSize: bgn.SizeValueType;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class FileUploader {
|
|
20
|
+
static validate(config: FileUploaderConfigType) {
|
|
21
|
+
return [
|
|
22
|
+
bodyLimit({
|
|
23
|
+
maxSize: config.maxFilesSize,
|
|
24
|
+
onError: () => {
|
|
25
|
+
throw FileTooBigError;
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
createMiddleware(async (c, next) => {
|
|
30
|
+
const body = await c.req.raw.clone().formData();
|
|
31
|
+
|
|
32
|
+
const file = body.get("file");
|
|
33
|
+
|
|
34
|
+
if (!(file instanceof File)) {
|
|
35
|
+
throw InvalidFileMimeTypeError;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const contentType = new bgn.Mime(file.type);
|
|
39
|
+
const accepted = config.mimeTypes.some((acceptedMimeType) =>
|
|
40
|
+
new bgn.Mime(acceptedMimeType).isSatisfiedBy(contentType)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (!accepted) throw InvalidFileMimeTypeError;
|
|
44
|
+
return next();
|
|
45
|
+
}),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/http-logger.ts
CHANGED
|
@@ -52,13 +52,13 @@ export class HttpLogger {
|
|
|
52
52
|
let body: any;
|
|
53
53
|
|
|
54
54
|
try {
|
|
55
|
-
body = await c.req.json();
|
|
55
|
+
body = await c.req.raw.clone().json();
|
|
56
56
|
} catch (error) {}
|
|
57
57
|
|
|
58
58
|
const httpRequestBeforeMetadata = {
|
|
59
59
|
params: c.req.param(),
|
|
60
60
|
headers: _.omit(
|
|
61
|
-
c.req.raw.headers.toJSON(),
|
|
61
|
+
c.req.raw.clone().headers.toJSON(),
|
|
62
62
|
HttpLogger.uninformativeHeaders
|
|
63
63
|
),
|
|
64
64
|
body,
|
|
@@ -80,9 +80,9 @@ export class HttpLogger {
|
|
|
80
80
|
|
|
81
81
|
await next();
|
|
82
82
|
|
|
83
|
-
const cacheHitHeader = c.res
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const cacheHitHeader = c.res
|
|
84
|
+
.clone()
|
|
85
|
+
.headers.get(bg.CacheResponse.CACHE_HIT_HEADER);
|
|
86
86
|
|
|
87
87
|
const cacheHit =
|
|
88
88
|
cacheHitHeader === bg.CacheHitEnum.hit
|
|
@@ -99,7 +99,7 @@ export class HttpLogger {
|
|
|
99
99
|
cacheHit,
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
const serverTimingMs = c.res.headers.get("Server-Timing");
|
|
102
|
+
const serverTimingMs = c.res.clone().headers.get("Server-Timing");
|
|
103
103
|
|
|
104
104
|
const durationMsMatch =
|
|
105
105
|
serverTimingMs?.match(/dur=([0-9]*\.?[0-9]+)/) ?? undefined;
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as bgn from "@bgord/node";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createMiddleware } from "hono/factory";
|
|
4
|
+
import { getCookie } from "hono/cookie";
|
|
5
|
+
|
|
6
|
+
export type TranslationsKeyType = string;
|
|
7
|
+
export type TranslationsValueType = string;
|
|
8
|
+
export type TranslationsType = Record<
|
|
9
|
+
TranslationsKeyType,
|
|
10
|
+
TranslationsValueType
|
|
11
|
+
>;
|
|
12
|
+
|
|
13
|
+
export type TranslationPlaceholderType = string;
|
|
14
|
+
export type TranslationPlaceholderValueType = string | number;
|
|
15
|
+
export type TranslationVariableType = Record<
|
|
16
|
+
TranslationPlaceholderType,
|
|
17
|
+
TranslationPlaceholderValueType
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
export type I18nConfigType = {
|
|
21
|
+
translationsPath?: bgn.Schema.PathType;
|
|
22
|
+
defaultLanguage?: bgn.Schema.LanguageType;
|
|
23
|
+
supportedLanguages: Record<string, bgn.Schema.LanguageType>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type I18nVariablesType = {
|
|
27
|
+
language: bgn.Schema.LanguageType;
|
|
28
|
+
supportedLanguages: bgn.Schema.LanguageType[];
|
|
29
|
+
translationsPath: bgn.Schema.PathType;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class I18n {
|
|
33
|
+
static LANGUAGE_COOKIE_NAME = "accept-language";
|
|
34
|
+
|
|
35
|
+
static DEFAULT_TRANSLATIONS_PATH =
|
|
36
|
+
bgn.Schema.Path.parse("infra/translations");
|
|
37
|
+
|
|
38
|
+
static FALLBACK_LANGUAGE = "en";
|
|
39
|
+
|
|
40
|
+
static applyTo(config: I18nConfigType) {
|
|
41
|
+
return createMiddleware(async (c, next) => {
|
|
42
|
+
const translationsPath =
|
|
43
|
+
config?.translationsPath ?? I18n.DEFAULT_TRANSLATIONS_PATH;
|
|
44
|
+
|
|
45
|
+
const defaultLanguage = config?.defaultLanguage ?? I18n.FALLBACK_LANGUAGE;
|
|
46
|
+
|
|
47
|
+
const chosenLanguage =
|
|
48
|
+
getCookie(c, I18n.LANGUAGE_COOKIE_NAME) ?? defaultLanguage;
|
|
49
|
+
|
|
50
|
+
const language = Object.keys(config.supportedLanguages).find(
|
|
51
|
+
(language) => language === chosenLanguage
|
|
52
|
+
)
|
|
53
|
+
? chosenLanguage
|
|
54
|
+
: I18n.FALLBACK_LANGUAGE;
|
|
55
|
+
|
|
56
|
+
c.set("supportedLanguages", Object.keys(config.supportedLanguages));
|
|
57
|
+
c.set("language", language);
|
|
58
|
+
c.set("translationsPath", translationsPath);
|
|
59
|
+
|
|
60
|
+
return next();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async getTranslations(
|
|
65
|
+
language: bgn.Schema.LanguageType,
|
|
66
|
+
translationsPath: bgn.Schema.PathType
|
|
67
|
+
): Promise<TranslationsType> {
|
|
68
|
+
try {
|
|
69
|
+
return Bun.file(
|
|
70
|
+
I18n.getTranslationPathForLanguage(language, translationsPath)
|
|
71
|
+
).json();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// biome-ignore lint: lint/suspicious/noConsoleLog
|
|
74
|
+
console.log("I18n#getTranslations", error);
|
|
75
|
+
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static useTranslations(translations: TranslationsType) {
|
|
81
|
+
return function translate(
|
|
82
|
+
key: TranslationsKeyType,
|
|
83
|
+
variables?: TranslationVariableType
|
|
84
|
+
) {
|
|
85
|
+
const translation = translations[key];
|
|
86
|
+
|
|
87
|
+
if (!translation) {
|
|
88
|
+
console.warn(`[@bgord/node] missing translation for key: ${key}`);
|
|
89
|
+
return key;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!variables) return translation;
|
|
93
|
+
|
|
94
|
+
return Object.entries(variables).reduce(
|
|
95
|
+
(result, [placeholder, value]) =>
|
|
96
|
+
result.replace(`{{${placeholder}}}`, String(value)),
|
|
97
|
+
translation
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static getTranslationPathForLanguage(
|
|
103
|
+
language: bgn.Schema.LanguageType,
|
|
104
|
+
translationsPath = I18n.DEFAULT_TRANSLATIONS_PATH
|
|
105
|
+
): bgn.Schema.PathType {
|
|
106
|
+
return bgn.Schema.Path.parse(
|
|
107
|
+
path.join(translationsPath, `${language}.json`)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./api-key-shield";
|
|
2
2
|
export * from "./api-version";
|
|
3
|
-
export * from "./
|
|
3
|
+
export * from "./auth-shield";
|
|
4
4
|
export * from "./context";
|
|
5
5
|
export * from "./etag-extractor";
|
|
6
|
+
export * from "./graceful-shutdown";
|
|
7
|
+
export * from "./healthcheck";
|
|
6
8
|
export * from "./http-logger";
|
|
7
|
-
export * from "./api-key-shield";
|
|
8
9
|
export * from "./rate-limit-shield";
|
|
9
|
-
export * from "./
|
|
10
|
+
export * from "./time-zone-offset";
|
|
11
|
+
export * from "./cache-static-files";
|
|
12
|
+
export * from "./cache-response";
|
|
13
|
+
export * from "./download-file";
|
|
14
|
+
export * from "./i18n";
|
|
15
|
+
export * from "./file-uploader";
|
|
16
|
+
export * from "./image-processor";
|