@ecashlib/shared-message 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/message/index.d.ts +4 -0
- package/dist/message/index.d.ts.map +1 -0
- package/dist/message/index.js +19 -0
- package/dist/message/message-error.d.ts +53 -0
- package/dist/message/message-error.d.ts.map +1 -0
- package/dist/message/message-error.js +76 -0
- package/dist/message/message-helper.d.ts +63 -0
- package/dist/message/message-helper.d.ts.map +1 -0
- package/dist/message/message-helper.js +121 -0
- package/dist/message/types.d.ts +40 -0
- package/dist/message/types.d.ts.map +1 -0
- package/dist/message/types.js +2 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# @ecash/shared
|
|
2
|
+
|
|
3
|
+
Shared utilities for Ecash microservices - Message handling, i18n, error helpers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **i18n Message System**: Get localized messages from database with caching
|
|
8
|
+
- **Parameter Replacement**: Dynamic message templates with `{placeholder}` support
|
|
9
|
+
- **Language Detection**: Auto-detect language from request headers or query params
|
|
10
|
+
- **Error Helpers**: Throw errors with dynamic, localized messages
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ecash/shared
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
### 1. Prepare Database Messages
|
|
21
|
+
|
|
22
|
+
Create messages in your `messages` collection:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"code": "ECASH_00001",
|
|
27
|
+
"classificationCode": "ECASH",
|
|
28
|
+
"content": {
|
|
29
|
+
"vi": "Metadata không hợp lệ theo schema của project type '{projectTypeCode}': {errors}",
|
|
30
|
+
"en": "Metadata is invalid according to project type '{projectTypeCode}' schema: {errors}",
|
|
31
|
+
"ko": "프로젝트 유형 '{projectTypeCode}'의 스키마에 따라 메타데이터가 유효하지 않습니다: {errors}"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Implement MessageServicePort
|
|
37
|
+
|
|
38
|
+
Each microservice implements the message service:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// ecash-marketplace/src/application/services/message/get-message-content.service.ts
|
|
42
|
+
import { MessageServicePort } from "@ecash/shared";
|
|
43
|
+
|
|
44
|
+
export class GetMessageContentService implements MessageServicePort {
|
|
45
|
+
async getMessageContentByCode(code: string, language: string) {
|
|
46
|
+
// Your implementation - query DB/Redis
|
|
47
|
+
const cached = await redis.get(`message:${code}:${language}`);
|
|
48
|
+
if (cached) return JSON.parse(cached);
|
|
49
|
+
|
|
50
|
+
const message = await db.collection("messages").findOne({ code });
|
|
51
|
+
return {
|
|
52
|
+
code: message.code,
|
|
53
|
+
classificationCode: message.classificationCode,
|
|
54
|
+
message: message.content[language],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### 1. Get Language from Request
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { getLanguageFromRequest } from "@ecash/shared";
|
|
66
|
+
|
|
67
|
+
const language = getLanguageFromRequest(request); // "vi", "en", "ko"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Get Localized Message
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { getMessage } from "@ecash/shared";
|
|
74
|
+
|
|
75
|
+
const message = await getMessage(messageService, "ECASH_00001", "vi", {
|
|
76
|
+
projectTypeCode: "APARTMENT",
|
|
77
|
+
errors: "field required",
|
|
78
|
+
});
|
|
79
|
+
// Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Get Message from Request (Shorthand)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { getMessageFromRequest } from "@ecash/shared";
|
|
86
|
+
|
|
87
|
+
const message = await getMessageFromRequest(
|
|
88
|
+
request,
|
|
89
|
+
messageService,
|
|
90
|
+
"ECASH_00001",
|
|
91
|
+
{ projectTypeCode: "APARTMENT" },
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Throw Message Error
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { throwMessageError } from "@ecash/shared";
|
|
99
|
+
|
|
100
|
+
await throwMessageError(
|
|
101
|
+
messageService,
|
|
102
|
+
"vi",
|
|
103
|
+
"ECASH_00001",
|
|
104
|
+
{ projectTypeCode: "APARTMENT", errors: "field required" },
|
|
105
|
+
"COMM0400",
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 5. Create Message Error (without throwing)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { createMessageError } from "@ecash/shared";
|
|
113
|
+
|
|
114
|
+
const error = await createMessageError(messageService, "vi", {
|
|
115
|
+
messageCode: "ECASH_00001",
|
|
116
|
+
responseCode: "COMM0400",
|
|
117
|
+
params: { projectTypeCode: "APARTMENT" },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
throw error;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### `getLanguageFromRequest(request, defaultLang?)`
|
|
126
|
+
|
|
127
|
+
Extract language from request headers or query params.
|
|
128
|
+
|
|
129
|
+
- **Priority**: `Accept-Language` header > `?lang=` query > `defaultLang`
|
|
130
|
+
- **Returns**: Language code (`"vi"`, `"en"`, `"ko"`)
|
|
131
|
+
|
|
132
|
+
### `getMessage(messageService, code, language, params?)`
|
|
133
|
+
|
|
134
|
+
Get localized message with parameter replacement.
|
|
135
|
+
|
|
136
|
+
- **messageService**: Service implementing `MessageServicePort`
|
|
137
|
+
- **code**: Message code from database
|
|
138
|
+
- **language**: Target language
|
|
139
|
+
- **params**: Object with key-value pairs to replace `{key}` in template
|
|
140
|
+
|
|
141
|
+
### `getMessageFromRequest(request, messageService, code, params?)`
|
|
142
|
+
|
|
143
|
+
Shorthand that extracts language from request and gets message.
|
|
144
|
+
|
|
145
|
+
### `throwMessageError(messageService, language, messageCode, params?, responseCode?, statusCode?)`
|
|
146
|
+
|
|
147
|
+
Throw error with localized message from database.
|
|
148
|
+
|
|
149
|
+
### `createMessageError(messageService, language, options)`
|
|
150
|
+
|
|
151
|
+
Create `MessageError` object without throwing.
|
|
152
|
+
|
|
153
|
+
## Language Detection
|
|
154
|
+
|
|
155
|
+
The library detects language in this order:
|
|
156
|
+
|
|
157
|
+
1. **Header**: `Accept-Language: vi-VN, vi;q=0.9` → `"vi"`
|
|
158
|
+
2. **Query**: `?lang=en` → `"en"`
|
|
159
|
+
3. **Default**: `"vi"`
|
|
160
|
+
|
|
161
|
+
## Message Template Syntax
|
|
162
|
+
|
|
163
|
+
Use `{key}` placeholders in your message templates:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"content": {
|
|
168
|
+
"vi": "Xin chào {name}, bạn có {count} tin nhắn mới",
|
|
169
|
+
"en": "Hello {name}, you have {count} new messages"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
getMessage(messageService, "MSG_00001", "vi", { name: "John", count: 5 });
|
|
176
|
+
// "Xin chào John, bạn có 5 tin nhắn mới"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Publishing
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
cd ecash-shared
|
|
183
|
+
npm run build
|
|
184
|
+
npm publish
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
PRIVATE - Ecash internal use only.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./message"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/message/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./message-helper"), exports);
|
|
19
|
+
__exportStar(require("./message-error"), exports);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MessageErrorOptions, MessageServicePort } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Error class for message-based errors
|
|
4
|
+
*/
|
|
5
|
+
export declare class MessageError extends Error {
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly statusCode: number;
|
|
8
|
+
readonly messageCode: string;
|
|
9
|
+
constructor(code: string, message: string, statusCode?: number, messageCode?: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a message error with localized message from DB
|
|
13
|
+
* Use this to throw errors with dynamic, multi-language messages
|
|
14
|
+
*
|
|
15
|
+
* @param messageService - Message service from DB
|
|
16
|
+
* @param language - Language code
|
|
17
|
+
* @param options - Error options
|
|
18
|
+
* @returns MessageError object
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* throw await createMessageError(
|
|
23
|
+
* fastify.getMessageContentByCodeService,
|
|
24
|
+
* "vi",
|
|
25
|
+
* {
|
|
26
|
+
* messageCode: "ECASH_00001",
|
|
27
|
+
* responseCode: "COMM0400",
|
|
28
|
+
* params: { projectTypeCode: "APARTMENT" }
|
|
29
|
+
* }
|
|
30
|
+
* );
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function createMessageError(messageService: MessageServicePort, language: string, options: MessageErrorOptions): Promise<MessageError>;
|
|
34
|
+
/**
|
|
35
|
+
* Create and throw a message error in one call
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* await throwMessageError(
|
|
40
|
+
* request,
|
|
41
|
+
* fastify.getMessageContentByCodeService,
|
|
42
|
+
* "ECASH_00001",
|
|
43
|
+
* { projectTypeCode: "APARTMENT" },
|
|
44
|
+
* "COMM0400"
|
|
45
|
+
* );
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function throwMessageError(request: Request, messageService: MessageServicePort, messageCode: string, params?: Record<string, string | number | undefined>, responseCode?: string, statusCode?: number): Promise<never>;
|
|
49
|
+
/**
|
|
50
|
+
* Simplified version - throw with language directly
|
|
51
|
+
*/
|
|
52
|
+
export declare function throwMessageError(messageService: MessageServicePort, language: string, messageCode: string, params?: Record<string, string | number | undefined>, responseCode?: string, statusCode?: number): Promise<never>;
|
|
53
|
+
//# sourceMappingURL=message-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-error.d.ts","sourceRoot":"","sources":["../../src/message/message-error.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElE;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,WAAW,EAAE,MAAM,CAAC;gBAGlC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAY,EACxB,WAAW,GAAE,MAAW;CAQ3B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,kBAAkB,EAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAgBvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,kBAAkB,EAClC,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,EACpD,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC,CAAC;AAElB;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,cAAc,EAAE,kBAAkB,EAClC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,EACpD,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageError = void 0;
|
|
4
|
+
exports.createMessageError = createMessageError;
|
|
5
|
+
exports.throwMessageError = throwMessageError;
|
|
6
|
+
const message_helper_1 = require("./message-helper");
|
|
7
|
+
/**
|
|
8
|
+
* Error class for message-based errors
|
|
9
|
+
*/
|
|
10
|
+
class MessageError extends Error {
|
|
11
|
+
constructor(code, message, statusCode = 400, messageCode = "") {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "MessageError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.messageCode = messageCode;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.MessageError = MessageError;
|
|
20
|
+
/**
|
|
21
|
+
* Create a message error with localized message from DB
|
|
22
|
+
* Use this to throw errors with dynamic, multi-language messages
|
|
23
|
+
*
|
|
24
|
+
* @param messageService - Message service from DB
|
|
25
|
+
* @param language - Language code
|
|
26
|
+
* @param options - Error options
|
|
27
|
+
* @returns MessageError object
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* throw await createMessageError(
|
|
32
|
+
* fastify.getMessageContentByCodeService,
|
|
33
|
+
* "vi",
|
|
34
|
+
* {
|
|
35
|
+
* messageCode: "ECASH_00001",
|
|
36
|
+
* responseCode: "COMM0400",
|
|
37
|
+
* params: { projectTypeCode: "APARTMENT" }
|
|
38
|
+
* }
|
|
39
|
+
* );
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
async function createMessageError(messageService, language, options) {
|
|
43
|
+
const { messageCode, responseCode = "COMM0400", statusCode = 400, params, } = options;
|
|
44
|
+
const message = await (0, message_helper_1.getMessage)(messageService, messageCode, language, params);
|
|
45
|
+
return new MessageError(responseCode, message, statusCode, messageCode);
|
|
46
|
+
}
|
|
47
|
+
async function throwMessageError(arg1, arg2, arg3, arg4, arg5, arg6) {
|
|
48
|
+
let messageService;
|
|
49
|
+
let language;
|
|
50
|
+
let messageCode;
|
|
51
|
+
let params = {};
|
|
52
|
+
let responseCode = "COMM0400";
|
|
53
|
+
let statusCode = 400;
|
|
54
|
+
// Overload resolution
|
|
55
|
+
if (typeof arg2 === "string") {
|
|
56
|
+
// Simplified: (messageService, language, messageCode, params?, responseCode?, statusCode?)
|
|
57
|
+
messageService = arg1;
|
|
58
|
+
language = arg2;
|
|
59
|
+
messageCode = arg3;
|
|
60
|
+
params = arg4 || {};
|
|
61
|
+
responseCode = arg5 || "COMM0400";
|
|
62
|
+
statusCode = arg6 || 400;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Full: (request, messageService, messageCode, params?, responseCode?, statusCode?)
|
|
66
|
+
// Note: This would need getLanguageFromRequest, but to avoid circular dependency
|
|
67
|
+
// we'll use the simplified version primarily
|
|
68
|
+
throw new Error("Use simplified signature: (messageService, language, messageCode, params?, responseCode?, statusCode?)");
|
|
69
|
+
}
|
|
70
|
+
throw await createMessageError(messageService, language, {
|
|
71
|
+
messageCode,
|
|
72
|
+
responseCode,
|
|
73
|
+
statusCode,
|
|
74
|
+
params,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { FastifyRequest } from "fastify";
|
|
2
|
+
import { MessageParams, MessageServicePort } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Extract language from request
|
|
5
|
+
* Priority: Header Accept-Language > Query param ?lang > Default
|
|
6
|
+
*
|
|
7
|
+
* @param request - Fastify request object
|
|
8
|
+
* @param defaultLang - Default language (default: "vi")
|
|
9
|
+
* @returns Language code (vi, en, ko, etc.)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const lang = getLanguageFromRequest(request); // "vi"
|
|
14
|
+
* const lang = getLanguageFromRequest(request, "en"); // "en"
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function getLanguageFromRequest(request: FastifyRequest, defaultLang?: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Get message from service and replace parameters
|
|
20
|
+
*
|
|
21
|
+
* @param messageService - Message service from DB
|
|
22
|
+
* @param code - Message code (ECASH_00001)
|
|
23
|
+
* @param language - Language code (vi, en, ko)
|
|
24
|
+
* @param params - Parameters to replace {key} with value
|
|
25
|
+
* @returns Localized message with parameters replaced
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const message = await getMessage(
|
|
30
|
+
* fastify.getMessageContentByCodeService,
|
|
31
|
+
* "ECASH_00001",
|
|
32
|
+
* "vi",
|
|
33
|
+
* { projectTypeCode: "APARTMENT", errors: "field required" }
|
|
34
|
+
* );
|
|
35
|
+
* // Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function getMessage(messageService: MessageServicePort, code: string, language: string, params?: MessageParams): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Replace {key} placeholders in message template with actual values
|
|
41
|
+
*
|
|
42
|
+
* @param template - Message template with {placeholders}
|
|
43
|
+
* @param params - Key-value pairs to replace
|
|
44
|
+
* @returns Formatted message
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* formatMessage("Hello {name}, you have {count} messages", { name: "John", count: 5 });
|
|
49
|
+
* // Returns: "Hello John, you have 5 messages"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function formatMessage(template: string, params?: MessageParams): string;
|
|
53
|
+
/**
|
|
54
|
+
* Shorthand to get message from request directly
|
|
55
|
+
*
|
|
56
|
+
* @param request - Fastify request
|
|
57
|
+
* @param messageService - Message service
|
|
58
|
+
* @param code - Message code
|
|
59
|
+
* @param params - Parameters
|
|
60
|
+
* @returns Localized message
|
|
61
|
+
*/
|
|
62
|
+
export declare function getMessageFromRequest(request: FastifyRequest, messageService: MessageServicePort, code: string, params?: MessageParams): Promise<string>;
|
|
63
|
+
//# sourceMappingURL=message-helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-helper.d.ts","sourceRoot":"","sources":["../../src/message/message-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,WAAW,GAAE,MAAa,GACzB,MAAM,CAgBR;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAC9B,cAAc,EAAE,kBAAkB,EAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CAeR;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,kBAAkB,EAClC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CAGjB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLanguageFromRequest = getLanguageFromRequest;
|
|
4
|
+
exports.getMessage = getMessage;
|
|
5
|
+
exports.formatMessage = formatMessage;
|
|
6
|
+
exports.getMessageFromRequest = getMessageFromRequest;
|
|
7
|
+
/**
|
|
8
|
+
* Extract language from request
|
|
9
|
+
* Priority: Header Accept-Language > Query param ?lang > Default
|
|
10
|
+
*
|
|
11
|
+
* @param request - Fastify request object
|
|
12
|
+
* @param defaultLang - Default language (default: "vi")
|
|
13
|
+
* @returns Language code (vi, en, ko, etc.)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const lang = getLanguageFromRequest(request); // "vi"
|
|
18
|
+
* const lang = getLanguageFromRequest(request, "en"); // "en"
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function getLanguageFromRequest(request, defaultLang = "vi") {
|
|
22
|
+
// 1. Header Accept-Language
|
|
23
|
+
const headerLang = request.headers["accept-language"];
|
|
24
|
+
if (headerLang) {
|
|
25
|
+
// Parse "vi-VN, vi;q=0.9, en;q=0.8" -> "vi"
|
|
26
|
+
return headerLang.split(",")[0].split("-")[0].trim();
|
|
27
|
+
}
|
|
28
|
+
// 2. Query param ?lang=vi
|
|
29
|
+
const queryLang = request.query?.lang;
|
|
30
|
+
if (queryLang && typeof queryLang === "string") {
|
|
31
|
+
return queryLang;
|
|
32
|
+
}
|
|
33
|
+
// 3. Default
|
|
34
|
+
return defaultLang;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get message from service and replace parameters
|
|
38
|
+
*
|
|
39
|
+
* @param messageService - Message service from DB
|
|
40
|
+
* @param code - Message code (ECASH_00001)
|
|
41
|
+
* @param language - Language code (vi, en, ko)
|
|
42
|
+
* @param params - Parameters to replace {key} with value
|
|
43
|
+
* @returns Localized message with parameters replaced
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const message = await getMessage(
|
|
48
|
+
* fastify.getMessageContentByCodeService,
|
|
49
|
+
* "ECASH_00001",
|
|
50
|
+
* "vi",
|
|
51
|
+
* { projectTypeCode: "APARTMENT", errors: "field required" }
|
|
52
|
+
* );
|
|
53
|
+
* // Returns: "Metadata không hợp lệ theo schema của project type 'APARTMENT': field required"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
async function getMessage(messageService, code, language, params) {
|
|
57
|
+
try {
|
|
58
|
+
const messageData = await messageService.getMessageContentByCode(code, language);
|
|
59
|
+
if (messageData?.message) {
|
|
60
|
+
return formatMessage(messageData.message, params);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// Log error but continue to fallback
|
|
65
|
+
console.error(`Error getting message ${code} in ${language}:`, error);
|
|
66
|
+
}
|
|
67
|
+
// Fallback to Vietnamese if available
|
|
68
|
+
if (language !== "vi") {
|
|
69
|
+
try {
|
|
70
|
+
const fallback = await messageService.getMessageContentByCode(code, "vi");
|
|
71
|
+
if (fallback?.message) {
|
|
72
|
+
return formatMessage(fallback.message, params);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(`Error getting fallback message ${code}:`, error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Final fallback - return code
|
|
80
|
+
return `[${code}]`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Replace {key} placeholders in message template with actual values
|
|
84
|
+
*
|
|
85
|
+
* @param template - Message template with {placeholders}
|
|
86
|
+
* @param params - Key-value pairs to replace
|
|
87
|
+
* @returns Formatted message
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* formatMessage("Hello {name}, you have {count} messages", { name: "John", count: 5 });
|
|
92
|
+
* // Returns: "Hello John, you have 5 messages"
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
function formatMessage(template, params) {
|
|
96
|
+
if (!params || Object.keys(params).length === 0) {
|
|
97
|
+
return template;
|
|
98
|
+
}
|
|
99
|
+
let result = template;
|
|
100
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
101
|
+
if (value !== undefined) {
|
|
102
|
+
const regex = new RegExp(`\\{${key}\\}`, "g");
|
|
103
|
+
result = result.replace(regex, String(value));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// Clean up any remaining {key} without values
|
|
107
|
+
return result.replace(/\{[^}]+\}/g, "?");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Shorthand to get message from request directly
|
|
111
|
+
*
|
|
112
|
+
* @param request - Fastify request
|
|
113
|
+
* @param messageService - Message service
|
|
114
|
+
* @param code - Message code
|
|
115
|
+
* @param params - Parameters
|
|
116
|
+
* @returns Localized message
|
|
117
|
+
*/
|
|
118
|
+
async function getMessageFromRequest(request, messageService, code, params) {
|
|
119
|
+
const language = getLanguageFromRequest(request);
|
|
120
|
+
return getMessage(messageService, code, language, params);
|
|
121
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameters for message template replacement
|
|
3
|
+
* Example: { projectTypeCode: "APARTMENT", errors: "field required" }
|
|
4
|
+
*/
|
|
5
|
+
export interface MessageParams {
|
|
6
|
+
[key: string]: string | number | undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Message content from database
|
|
10
|
+
*/
|
|
11
|
+
export interface MessageContent {
|
|
12
|
+
code: string;
|
|
13
|
+
classificationCode: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Options for getting a message
|
|
18
|
+
*/
|
|
19
|
+
export interface GetMessageOptions {
|
|
20
|
+
code: string;
|
|
21
|
+
language?: string;
|
|
22
|
+
params?: MessageParams;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Port interface for message service
|
|
26
|
+
* Each microservice implements this to connect to their DB
|
|
27
|
+
*/
|
|
28
|
+
export interface MessageServicePort {
|
|
29
|
+
getMessageContentByCode(code: string, language: string): Promise<MessageContent | null>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Error options for throwing message-based errors
|
|
33
|
+
*/
|
|
34
|
+
export interface MessageErrorOptions {
|
|
35
|
+
messageCode: string;
|
|
36
|
+
responseCode?: string;
|
|
37
|
+
statusCode?: number;
|
|
38
|
+
params?: MessageParams;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/message/types.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,uBAAuB,CACrB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ecashlib/shared-message",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared utilities for Ecash microservices - Message handling, i18n, error helpers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"watch": "tsc --watch",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
11
|
+
"clean": "rimraf dist"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"ecash",
|
|
15
|
+
"ecashlib",
|
|
16
|
+
"shared",
|
|
17
|
+
"i18n",
|
|
18
|
+
"message",
|
|
19
|
+
"error-handling"
|
|
20
|
+
],
|
|
21
|
+
"author": "Ecash Team",
|
|
22
|
+
"license": "PRIVATE",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"fastify": "^4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.0.0",
|
|
31
|
+
"typescript": "^5.0.0",
|
|
32
|
+
"rimraf": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md"
|
|
37
|
+
]
|
|
38
|
+
}
|