@nitronjs/framework 0.2.3 → 0.2.4
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 -1
- package/cli/create.js +88 -72
- package/cli/njs.js +13 -6
- package/lib/Auth/Auth.js +167 -0
- package/lib/Build/CssBuilder.js +9 -0
- package/lib/Build/FileAnalyzer.js +16 -0
- package/lib/Build/HydrationBuilder.js +17 -0
- package/lib/Build/Manager.js +15 -0
- package/lib/Build/colors.js +4 -0
- package/lib/Build/plugins.js +84 -20
- package/lib/Console/Commands/DevCommand.js +13 -9
- package/lib/Console/Commands/MakeCommand.js +24 -10
- package/lib/Console/Commands/MigrateCommand.js +0 -1
- package/lib/Console/Commands/MigrateFreshCommand.js +18 -25
- package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
- package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
- package/lib/Console/Commands/SeedCommand.js +4 -2
- package/lib/Console/Commands/StorageLinkCommand.js +20 -5
- package/lib/Console/Output.js +143 -0
- package/lib/Core/Config.js +2 -1
- package/lib/Core/Paths.js +8 -0
- package/lib/Database/DB.js +141 -51
- package/lib/Database/Drivers/MySQLDriver.js +102 -157
- package/lib/Database/Migration/Checksum.js +3 -8
- package/lib/Database/Migration/MigrationRepository.js +25 -35
- package/lib/Database/Migration/MigrationRunner.js +56 -61
- package/lib/Database/Model.js +157 -83
- package/lib/Database/QueryBuilder.js +31 -0
- package/lib/Database/QueryValidation.js +36 -44
- package/lib/Database/Schema/Blueprint.js +25 -36
- package/lib/Database/Schema/Manager.js +31 -68
- package/lib/Database/Seeder/SeederRunner.js +12 -31
- package/lib/Date/DateTime.js +9 -0
- package/lib/Encryption/Encryption.js +52 -0
- package/lib/Faker/Faker.js +11 -0
- package/lib/Filesystem/Storage.js +120 -0
- package/lib/HMR/Server.js +79 -9
- package/lib/Hashing/Hash.js +41 -0
- package/lib/Http/Server.js +177 -152
- package/lib/Logging/{Manager.js → Log.js} +68 -80
- package/lib/Mail/Mail.js +187 -0
- package/lib/Route/Router.js +416 -0
- package/lib/Session/File.js +135 -233
- package/lib/Session/Manager.js +117 -171
- package/lib/Session/Memory.js +28 -38
- package/lib/Session/Session.js +71 -107
- package/lib/Support/Str.js +103 -0
- package/lib/Translation/Lang.js +54 -0
- package/lib/View/Client/hmr-client.js +87 -51
- package/lib/View/Client/nitronjs-icon.png +0 -0
- package/lib/View/{Manager.js → View.js} +44 -29
- package/lib/index.d.ts +42 -8
- package/lib/index.js +19 -12
- package/package.json +1 -1
- package/skeleton/app/Controllers/HomeController.js +7 -1
- package/skeleton/resources/css/global.css +1 -0
- package/skeleton/resources/views/Site/Home.tsx +456 -79
- package/skeleton/tsconfig.json +6 -1
- package/lib/Auth/Manager.js +0 -111
- package/lib/Database/Connection.js +0 -61
- package/lib/Database/Manager.js +0 -162
- package/lib/Encryption/Manager.js +0 -47
- package/lib/Filesystem/Manager.js +0 -74
- package/lib/Hashing/Manager.js +0 -25
- package/lib/Mail/Manager.js +0 -120
- package/lib/Route/Loader.js +0 -80
- package/lib/Route/Manager.js +0 -286
- package/lib/Translation/Manager.js +0 -49
package/lib/Session/Session.js
CHANGED
|
@@ -1,114 +1,97 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Represents a single user session with secure data storage.
|
|
7
|
-
* All data is private and accessed through controlled methods.
|
|
8
|
-
*
|
|
9
|
-
* Features:
|
|
10
|
-
* - Private data storage (no external mutation)
|
|
11
|
-
* - CSRF token generation and verification
|
|
12
|
-
* - Flash messages (one-time messages)
|
|
13
|
-
* - Session regeneration (security)
|
|
14
|
-
* - Session destruction (logout)
|
|
4
|
+
* Represents a single user session.
|
|
5
|
+
* Provides secure data storage, CSRF protection, and flash messages.
|
|
15
6
|
*/
|
|
16
7
|
class Session {
|
|
17
|
-
// Private fields
|
|
18
8
|
#id;
|
|
19
9
|
#data;
|
|
20
10
|
#createdAt;
|
|
21
11
|
#lastActivity;
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
oldId: null
|
|
25
|
-
};
|
|
12
|
+
#oldId = null;
|
|
13
|
+
#regenerated = false;
|
|
26
14
|
|
|
27
|
-
constructor
|
|
15
|
+
constructor(id, sessionData) {
|
|
28
16
|
this.#id = id;
|
|
29
17
|
this.#data = sessionData.data || {};
|
|
30
18
|
this.#createdAt = sessionData.createdAt || Date.now();
|
|
31
19
|
this.#lastActivity = sessionData.lastActivity || null;
|
|
32
20
|
}
|
|
33
21
|
|
|
34
|
-
|
|
35
|
-
// Getters
|
|
36
|
-
// ========================================
|
|
37
|
-
|
|
38
|
-
get id () {
|
|
22
|
+
get id() {
|
|
39
23
|
return this.#id;
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
get createdAt
|
|
26
|
+
get createdAt() {
|
|
43
27
|
return this.#createdAt;
|
|
44
28
|
}
|
|
45
29
|
|
|
46
|
-
get lastActivity
|
|
30
|
+
get lastActivity() {
|
|
47
31
|
return this.#lastActivity;
|
|
48
32
|
}
|
|
49
33
|
|
|
50
|
-
// ========================================
|
|
51
|
-
// Data Management
|
|
52
|
-
// ========================================
|
|
53
|
-
|
|
54
34
|
/**
|
|
55
|
-
*
|
|
35
|
+
* Gets a session value.
|
|
36
|
+
* @param {string} key
|
|
37
|
+
* @returns {*}
|
|
56
38
|
*/
|
|
57
|
-
get
|
|
39
|
+
get(key) {
|
|
58
40
|
return this.#data[key];
|
|
59
41
|
}
|
|
60
42
|
|
|
61
43
|
/**
|
|
62
|
-
*
|
|
44
|
+
* Sets a session value.
|
|
45
|
+
* @param {string} key
|
|
46
|
+
* @param {*} value
|
|
63
47
|
*/
|
|
64
|
-
set
|
|
48
|
+
set(key, value) {
|
|
65
49
|
this.#data[key] = value;
|
|
66
50
|
}
|
|
67
51
|
|
|
68
52
|
/**
|
|
69
|
-
*
|
|
53
|
+
* Gets all session data (copy).
|
|
54
|
+
* @returns {Object}
|
|
70
55
|
*/
|
|
71
|
-
all
|
|
56
|
+
all() {
|
|
72
57
|
return { ...this.#data };
|
|
73
58
|
}
|
|
74
59
|
|
|
75
|
-
// ========================================
|
|
76
|
-
// CSRF Protection
|
|
77
|
-
// ========================================
|
|
78
|
-
|
|
79
60
|
/**
|
|
80
|
-
*
|
|
81
|
-
* @returns {string}
|
|
61
|
+
* Generates a new CSRF token.
|
|
62
|
+
* @returns {string}
|
|
82
63
|
*/
|
|
83
|
-
generateCsrfToken
|
|
84
|
-
const token = crypto.randomBytes(32).toString(
|
|
85
|
-
this.set(
|
|
64
|
+
generateCsrfToken() {
|
|
65
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
66
|
+
this.set("_csrf", token);
|
|
86
67
|
|
|
87
68
|
return token;
|
|
88
69
|
}
|
|
89
70
|
|
|
90
71
|
/**
|
|
91
|
-
*
|
|
92
|
-
* @returns {string}
|
|
72
|
+
* Gets the CSRF token, generating one if needed.
|
|
73
|
+
* @returns {string}
|
|
93
74
|
*/
|
|
94
|
-
getCsrfToken
|
|
95
|
-
return this.get(
|
|
75
|
+
getCsrfToken() {
|
|
76
|
+
return this.get("_csrf") || this.generateCsrfToken();
|
|
96
77
|
}
|
|
97
78
|
|
|
98
79
|
/**
|
|
99
|
-
*
|
|
100
|
-
* @param {string} token
|
|
101
|
-
* @returns {boolean}
|
|
80
|
+
* Verifies a CSRF token using timing-safe comparison.
|
|
81
|
+
* @param {string} token
|
|
82
|
+
* @returns {boolean}
|
|
102
83
|
*/
|
|
103
|
-
verifyCsrfToken
|
|
104
|
-
const sessionToken = this.get(
|
|
105
|
-
|
|
106
|
-
if (!sessionToken || !token)
|
|
107
|
-
|
|
84
|
+
verifyCsrfToken(token) {
|
|
85
|
+
const sessionToken = this.get("_csrf");
|
|
86
|
+
|
|
87
|
+
if (!sessionToken || !token) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
108
91
|
try {
|
|
109
92
|
return crypto.timingSafeEqual(
|
|
110
|
-
Buffer.from(sessionToken,
|
|
111
|
-
Buffer.from(token,
|
|
93
|
+
Buffer.from(sessionToken, "hex"),
|
|
94
|
+
Buffer.from(token, "hex")
|
|
112
95
|
);
|
|
113
96
|
}
|
|
114
97
|
catch {
|
|
@@ -116,80 +99,61 @@ class Session {
|
|
|
116
99
|
}
|
|
117
100
|
}
|
|
118
101
|
|
|
119
|
-
// ========================================
|
|
120
|
-
// Flash Messages
|
|
121
|
-
// ========================================
|
|
122
|
-
|
|
123
102
|
/**
|
|
124
|
-
*
|
|
125
|
-
* @param {string} key
|
|
126
|
-
* @param {
|
|
103
|
+
* Stores a flash message (available only for next request).
|
|
104
|
+
* @param {string} key
|
|
105
|
+
* @param {*} value
|
|
127
106
|
*/
|
|
128
|
-
flash
|
|
107
|
+
flash(key, value) {
|
|
129
108
|
if (!this.#data._flash) {
|
|
130
109
|
this.#data._flash = {};
|
|
131
110
|
}
|
|
132
|
-
|
|
111
|
+
|
|
133
112
|
this.#data._flash[key] = value;
|
|
134
113
|
}
|
|
135
114
|
|
|
136
115
|
/**
|
|
137
|
-
*
|
|
138
|
-
* @param {string} key
|
|
139
|
-
* @returns {
|
|
116
|
+
* Gets and removes a flash message.
|
|
117
|
+
* @param {string} key
|
|
118
|
+
* @returns {*}
|
|
140
119
|
*/
|
|
141
|
-
getFlash
|
|
142
|
-
if (!this.#data._flash?.[key])
|
|
143
|
-
|
|
144
|
-
|
|
120
|
+
getFlash(key) {
|
|
121
|
+
if (!this.#data._flash?.[key]) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
145
124
|
|
|
125
|
+
const value = this.#data._flash[key];
|
|
146
126
|
delete this.#data._flash[key];
|
|
147
127
|
|
|
148
128
|
return value;
|
|
149
129
|
}
|
|
150
130
|
|
|
151
|
-
// ========================================
|
|
152
|
-
// Lifecycle Management
|
|
153
|
-
// ========================================
|
|
154
|
-
|
|
155
131
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* Call this after login, logout, or any privilege escalation.
|
|
160
|
-
*
|
|
161
|
-
* @returns {object} {oldId, newId}
|
|
132
|
+
* Regenerates session ID and CSRF token.
|
|
133
|
+
* Prevents session fixation attacks.
|
|
134
|
+
* @returns {{ oldId: string, newId: string }}
|
|
162
135
|
*/
|
|
163
|
-
regenerate
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
oldId: this.#flags.oldId,
|
|
168
|
-
newId: this.#id
|
|
169
|
-
};
|
|
136
|
+
regenerate() {
|
|
137
|
+
if (this.#regenerated) {
|
|
138
|
+
return { oldId: this.#oldId, newId: this.#id };
|
|
170
139
|
}
|
|
171
|
-
|
|
172
|
-
this.#
|
|
173
|
-
this.#id = crypto.randomBytes(32).toString(
|
|
174
|
-
this.#
|
|
140
|
+
|
|
141
|
+
this.#oldId = this.#id;
|
|
142
|
+
this.#id = crypto.randomBytes(32).toString("hex");
|
|
143
|
+
this.#regenerated = true;
|
|
175
144
|
this.generateCsrfToken();
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
oldId: this.#flags.oldId,
|
|
179
|
-
newId: this.#id
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
145
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// ========================================
|
|
146
|
+
return { oldId: this.#oldId, newId: this.#id };
|
|
147
|
+
}
|
|
186
148
|
|
|
187
|
-
|
|
188
|
-
|
|
149
|
+
/** @private */
|
|
150
|
+
shouldRegenerate() {
|
|
151
|
+
return this.#regenerated;
|
|
189
152
|
}
|
|
190
153
|
|
|
191
|
-
|
|
192
|
-
|
|
154
|
+
/** @private */
|
|
155
|
+
getOldId() {
|
|
156
|
+
return this.#oldId;
|
|
193
157
|
}
|
|
194
158
|
}
|
|
195
159
|
|
package/lib/Support/Str.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String utility class with helper methods.
|
|
3
|
+
* Provides Laravel-style string manipulation functions.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* Str.slug("Hello World"); // "hello-world"
|
|
7
|
+
* Str.camel("user_name"); // "userName"
|
|
8
|
+
*/
|
|
1
9
|
class Str {
|
|
10
|
+
/**
|
|
11
|
+
* Generates a random alphanumeric string.
|
|
12
|
+
* @param {number} length - Desired string length
|
|
13
|
+
* @returns {string} Random string
|
|
14
|
+
*/
|
|
2
15
|
static random(length) {
|
|
3
16
|
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
4
17
|
let result = "";
|
|
@@ -11,6 +24,11 @@ class Str {
|
|
|
11
24
|
return result;
|
|
12
25
|
}
|
|
13
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Converts a string to URL-friendly slug.
|
|
29
|
+
* @param {string} text - Input text
|
|
30
|
+
* @returns {string} Slugified string
|
|
31
|
+
*/
|
|
14
32
|
static slug(text) {
|
|
15
33
|
const normalizedInput = text
|
|
16
34
|
.toLowerCase()
|
|
@@ -25,28 +43,55 @@ class Str {
|
|
|
25
43
|
return slug;
|
|
26
44
|
}
|
|
27
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Capitalizes the first character.
|
|
48
|
+
* @param {string} str - Input string
|
|
49
|
+
* @returns {string} String with first char uppercase
|
|
50
|
+
*/
|
|
28
51
|
static ucfirst(str) {
|
|
29
52
|
if (!str) return "";
|
|
53
|
+
|
|
30
54
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
31
55
|
}
|
|
32
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Lowercases the first character.
|
|
59
|
+
* @param {string} str - Input string
|
|
60
|
+
* @returns {string} String with first char lowercase
|
|
61
|
+
*/
|
|
33
62
|
static lcfirst(str) {
|
|
34
63
|
if (!str) return "";
|
|
64
|
+
|
|
35
65
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
36
66
|
}
|
|
37
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Converts to camelCase.
|
|
70
|
+
* @param {string} str - Input string
|
|
71
|
+
* @returns {string} camelCased string
|
|
72
|
+
*/
|
|
38
73
|
static camel(str) {
|
|
39
74
|
return str
|
|
40
75
|
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
41
76
|
.replace(/^(.)/, (c) => c.toLowerCase());
|
|
42
77
|
}
|
|
43
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Converts to PascalCase.
|
|
81
|
+
* @param {string} str - Input string
|
|
82
|
+
* @returns {string} PascalCased string
|
|
83
|
+
*/
|
|
44
84
|
static pascal(str) {
|
|
45
85
|
return str
|
|
46
86
|
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
47
87
|
.replace(/^(.)/, (c) => c.toUpperCase());
|
|
48
88
|
}
|
|
49
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Converts to snake_case.
|
|
92
|
+
* @param {string} str - Input string
|
|
93
|
+
* @returns {string} snake_cased string
|
|
94
|
+
*/
|
|
50
95
|
static snake(str) {
|
|
51
96
|
return str
|
|
52
97
|
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
@@ -54,6 +99,11 @@ class Str {
|
|
|
54
99
|
.toLowerCase();
|
|
55
100
|
}
|
|
56
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Converts to kebab-case.
|
|
104
|
+
* @param {string} str - Input string
|
|
105
|
+
* @returns {string} kebab-cased string
|
|
106
|
+
*/
|
|
57
107
|
static kebab(str) {
|
|
58
108
|
return str
|
|
59
109
|
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
@@ -61,38 +111,91 @@ class Str {
|
|
|
61
111
|
.toLowerCase();
|
|
62
112
|
}
|
|
63
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Limits string to specified length.
|
|
116
|
+
* @param {string} str - Input string
|
|
117
|
+
* @param {number} length - Maximum length
|
|
118
|
+
* @param {string} suffix - Suffix to append (default: "...")
|
|
119
|
+
* @returns {string} Truncated string
|
|
120
|
+
*/
|
|
64
121
|
static limit(str, length, suffix = "...") {
|
|
65
122
|
if (!str || str.length <= length) return str;
|
|
123
|
+
|
|
66
124
|
return str.substring(0, length) + suffix;
|
|
67
125
|
}
|
|
68
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Limits string to specified word count.
|
|
129
|
+
* @param {string} str - Input string
|
|
130
|
+
* @param {number} count - Maximum word count
|
|
131
|
+
* @param {string} suffix - Suffix to append (default: "...")
|
|
132
|
+
* @returns {string} Truncated string
|
|
133
|
+
*/
|
|
69
134
|
static words(str, count, suffix = "...") {
|
|
70
135
|
const wordsArray = str.split(/\s+/);
|
|
136
|
+
|
|
71
137
|
if (wordsArray.length <= count) return str;
|
|
138
|
+
|
|
72
139
|
return wordsArray.slice(0, count).join(" ") + suffix;
|
|
73
140
|
}
|
|
74
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Checks if string contains substring.
|
|
144
|
+
* @param {string} haystack - String to search in
|
|
145
|
+
* @param {string} needle - String to search for
|
|
146
|
+
* @returns {boolean} True if found
|
|
147
|
+
*/
|
|
75
148
|
static contains(haystack, needle) {
|
|
76
149
|
return haystack.includes(needle);
|
|
77
150
|
}
|
|
78
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Checks if string starts with substring.
|
|
154
|
+
* @param {string} haystack - String to check
|
|
155
|
+
* @param {string} needle - Prefix to look for
|
|
156
|
+
* @returns {boolean} True if starts with
|
|
157
|
+
*/
|
|
79
158
|
static startsWith(haystack, needle) {
|
|
80
159
|
return haystack.startsWith(needle);
|
|
81
160
|
}
|
|
82
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Checks if string ends with substring.
|
|
164
|
+
* @param {string} haystack - String to check
|
|
165
|
+
* @param {string} needle - Suffix to look for
|
|
166
|
+
* @returns {boolean} True if ends with
|
|
167
|
+
*/
|
|
83
168
|
static endsWith(haystack, needle) {
|
|
84
169
|
return haystack.endsWith(needle);
|
|
85
170
|
}
|
|
86
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Replaces first occurrence of search string.
|
|
174
|
+
* @param {string} str - Input string
|
|
175
|
+
* @param {string} search - String to find
|
|
176
|
+
* @param {string} replace - Replacement string
|
|
177
|
+
* @returns {string} Modified string
|
|
178
|
+
*/
|
|
87
179
|
static replaceFirst(str, search, replace) {
|
|
88
180
|
const pos = str.indexOf(search);
|
|
181
|
+
|
|
89
182
|
if (pos === -1) return str;
|
|
183
|
+
|
|
90
184
|
return str.substring(0, pos) + replace + str.substring(pos + search.length);
|
|
91
185
|
}
|
|
92
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Replaces last occurrence of search string.
|
|
189
|
+
* @param {string} str - Input string
|
|
190
|
+
* @param {string} search - String to find
|
|
191
|
+
* @param {string} replace - Replacement string
|
|
192
|
+
* @returns {string} Modified string
|
|
193
|
+
*/
|
|
93
194
|
static replaceLast(str, search, replace) {
|
|
94
195
|
const pos = str.lastIndexOf(search);
|
|
196
|
+
|
|
95
197
|
if (pos === -1) return str;
|
|
198
|
+
|
|
96
199
|
return str.substring(0, pos) + replace + str.substring(pos + search.length);
|
|
97
200
|
}
|
|
98
201
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import Paths from "../Core/Paths.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Translation manager for multi-language support.
|
|
7
|
+
* Loads translations from JSON files and supports parameter replacement.
|
|
8
|
+
*/
|
|
9
|
+
class Lang {
|
|
10
|
+
static #cache = new Map();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets a translated string for the current request language.
|
|
14
|
+
* @param {import("fastify").FastifyRequest} req - Fastify request with language property
|
|
15
|
+
* @param {string} key - Translation key to look up
|
|
16
|
+
* @param {Object|null} params - Optional parameters to replace in translation (e.g., { name: "John" } for ":name")
|
|
17
|
+
* @returns {string} Translated string or the key if not found
|
|
18
|
+
*/
|
|
19
|
+
static get(req, key, params = null) {
|
|
20
|
+
const currentLang = req.language;
|
|
21
|
+
const langPath = path.join(Paths.langs, currentLang + ".json");
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(langPath)) {
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let translations = this.#cache.get(currentLang);
|
|
28
|
+
|
|
29
|
+
if (!translations) {
|
|
30
|
+
const langFile = fs.readFileSync(langPath, { encoding: "utf-8", flag: "r" });
|
|
31
|
+
translations = JSON.parse(langFile);
|
|
32
|
+
this.#cache.set(currentLang, translations);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let translation = translations[key] || key;
|
|
36
|
+
|
|
37
|
+
if (params) {
|
|
38
|
+
for (const [paramKey, paramValue] of Object.entries(params)) {
|
|
39
|
+
translation = translation.replaceAll(":" + paramKey, paramValue);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return translation;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Clears the translation cache. Useful for development hot reload.
|
|
48
|
+
*/
|
|
49
|
+
static clearCache() {
|
|
50
|
+
this.#cache.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default Lang;
|