schema-dsl 2.0.0 → 2.0.1
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/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import type { JSONSchema } from '../types/schema.js'
|
|
2
|
-
import { Validator } from './Validator.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* JSONSchemaCore — v1 compatibility facade.
|
|
6
|
-
*
|
|
7
|
-
* The v2 internals have been split into DslParser / SchemaCompiler / Validator; this class
|
|
8
|
-
* restores the commonly-used chainable entry points from the v1 public API so that users
|
|
9
|
-
* who import from the main entry point do not encounter errors.
|
|
10
|
-
*/
|
|
11
|
-
export class JSONSchemaCore {
|
|
12
|
-
schema: JSONSchema
|
|
13
|
-
|
|
14
|
-
constructor(schema: JSONSchema = {}) {
|
|
15
|
-
this.schema = { ...schema }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type(typeName: string): this {
|
|
19
|
-
this.schema.type = typeName
|
|
20
|
-
return this
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
property(name: string, schema: JSONSchema): this {
|
|
24
|
-
if (!this.schema.properties) this.schema.properties = {}
|
|
25
|
-
this.schema.properties[name] = schema
|
|
26
|
-
return this
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
properties(properties: Record<string, JSONSchema>): this {
|
|
30
|
-
this.schema.properties = { ...(this.schema.properties ?? {}), ...properties }
|
|
31
|
-
return this
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
required(fields: string[] | string): this {
|
|
35
|
-
this.schema.required = Array.isArray(fields) ? fields : [fields]
|
|
36
|
-
return this
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
format(formatName: string): this {
|
|
40
|
-
this.schema.format = formatName
|
|
41
|
-
return this
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
pattern(pattern: RegExp | string): this {
|
|
45
|
-
this.schema.pattern = pattern instanceof RegExp ? pattern.source : pattern
|
|
46
|
-
return this
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
items(schema: JSONSchema): this {
|
|
50
|
-
this.schema.items = schema
|
|
51
|
-
return this
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
toSchema(): JSONSchema {
|
|
55
|
-
return this.schema
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getSchema(): JSONSchema {
|
|
59
|
-
return this.toSchema()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
validate(data: unknown): ReturnType<Validator['validate']> {
|
|
63
|
-
return new Validator().validate(this.schema, data)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
import type { JSONSchema } from '../types/schema.js'
|
|
2
|
+
import { Validator } from './Validator.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* JSONSchemaCore — v1 compatibility facade.
|
|
6
|
+
*
|
|
7
|
+
* The v2 internals have been split into DslParser / SchemaCompiler / Validator; this class
|
|
8
|
+
* restores the commonly-used chainable entry points from the v1 public API so that users
|
|
9
|
+
* who import from the main entry point do not encounter errors.
|
|
10
|
+
*/
|
|
11
|
+
export class JSONSchemaCore {
|
|
12
|
+
schema: JSONSchema
|
|
13
|
+
|
|
14
|
+
constructor(schema: JSONSchema = {}) {
|
|
15
|
+
this.schema = { ...schema }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type(typeName: string): this {
|
|
19
|
+
this.schema.type = typeName
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
property(name: string, schema: JSONSchema): this {
|
|
24
|
+
if (!this.schema.properties) this.schema.properties = {}
|
|
25
|
+
this.schema.properties[name] = schema
|
|
26
|
+
return this
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
properties(properties: Record<string, JSONSchema>): this {
|
|
30
|
+
this.schema.properties = { ...(this.schema.properties ?? {}), ...properties }
|
|
31
|
+
return this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
required(fields: string[] | string): this {
|
|
35
|
+
this.schema.required = Array.isArray(fields) ? fields : [fields]
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
format(formatName: string): this {
|
|
40
|
+
this.schema.format = formatName
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pattern(pattern: RegExp | string): this {
|
|
45
|
+
this.schema.pattern = pattern instanceof RegExp ? pattern.source : pattern
|
|
46
|
+
return this
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
items(schema: JSONSchema): this {
|
|
50
|
+
this.schema.items = schema
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toSchema(): JSONSchema {
|
|
55
|
+
return this.schema
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getSchema(): JSONSchema {
|
|
59
|
+
return this.toSchema()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
validate(data: unknown): ReturnType<Validator['validate']> {
|
|
63
|
+
return new Validator().validate(this.schema, data)
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/core/Locale.ts
CHANGED
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
import type { LocaleKey, LocaleMessage } from '../locales/types.js'
|
|
2
|
-
import { getMessage, getMessages, isSupportedLocale, getSupportedLocales } from '../locales/index.js'
|
|
3
|
-
|
|
4
|
-
export interface LocaleResolvedMessage {
|
|
5
|
-
code: string | number
|
|
6
|
-
message: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Locale — global locale manager (static class).
|
|
11
|
-
*
|
|
12
|
-
* v1 compatibility semantics:
|
|
13
|
-
* - getMessage() → returns { code, message } for every resolved message
|
|
14
|
-
* - getMessageText() → always returns final message text (used internally by v2)
|
|
15
|
-
* - getMessageConfig() → returns raw LocaleMessage (may contain code object; used by I18nError)
|
|
16
|
-
*/
|
|
17
|
-
export const DEFAULT_LOCALE = 'en-US'
|
|
18
|
-
|
|
19
|
-
export class Locale {
|
|
20
|
-
private static _currentLocale: string = DEFAULT_LOCALE
|
|
21
|
-
private static _customMessages: Record<string, LocaleMessage> = {}
|
|
22
|
-
|
|
23
|
-
/** v1 compat: expose custom messages */
|
|
24
|
-
static get customMessages(): Record<string, LocaleMessage> {
|
|
25
|
-
return this._customMessages
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** v1 compat: expose all locales as { locale: messages } map */
|
|
29
|
-
static get locales(): Record<string, Record<string, LocaleMessage>> {
|
|
30
|
-
const result: Record<string, Record<string, LocaleMessage>> = {}
|
|
31
|
-
// Built-in locales
|
|
32
|
-
for (const locale of getSupportedLocales()) {
|
|
33
|
-
result[locale] = getMessages(locale) as Record<string, LocaleMessage>
|
|
34
|
-
}
|
|
35
|
-
// Custom locales added via addLocale
|
|
36
|
-
for (const key of Object.keys(this._customMessages)) {
|
|
37
|
-
if (key.includes(':')) {
|
|
38
|
-
const colonIdx = key.indexOf(':')
|
|
39
|
-
const locale = key.substring(0, colonIdx)
|
|
40
|
-
const msgKey = key.substring(colonIdx + 1)
|
|
41
|
-
if (!result[locale]) result[locale] = {}
|
|
42
|
-
result[locale][msgKey] = this._customMessages[key]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return result
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ─── Locale Switching ─────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
static setLocale(locale: string): void {
|
|
51
|
-
this._currentLocale = locale
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
static getLocale(): string {
|
|
55
|
-
return this._currentLocale
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ─── Custom Messages (global override) ───────────────────────────────────
|
|
59
|
-
|
|
60
|
-
static setMessages(messages: Record<string, LocaleMessage>): void {
|
|
61
|
-
this._customMessages = { ...this._customMessages, ...messages }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static addLocale(locale: string, messages: Record<string, LocaleMessage>): void {
|
|
65
|
-
// Dynamically add a locale pack at runtime (merged into existing entries).
|
|
66
|
-
// Records into customMessages and takes priority during lookup.
|
|
67
|
-
for (const [k, v] of Object.entries(messages)) {
|
|
68
|
-
this._customMessages[`${locale}:${k}`] = v
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
static getAvailableLocales(): string[] {
|
|
73
|
-
return getSupportedLocales()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
static isSupportedLocale(locale: string): boolean {
|
|
77
|
-
return isSupportedLocale(locale)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ─── Core Query Methods ───────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get a resolved message (v1 compat: returns { code, message } on hit).
|
|
84
|
-
*
|
|
85
|
-
* Priority: custom messages > locale pack > key itself.
|
|
86
|
-
*/
|
|
87
|
-
static getMessage(
|
|
88
|
-
type: string,
|
|
89
|
-
customMessages: Record<string, LocaleMessage> = {},
|
|
90
|
-
locale: string | null = null
|
|
91
|
-
): LocaleResolvedMessage | string {
|
|
92
|
-
const resolved = this._resolveMessage(type, customMessages, locale)
|
|
93
|
-
if (!resolved) return type
|
|
94
|
-
return this._normalizeResolvedMessage(type, resolved)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Get the final message text (used internally by v2 to avoid "[object Object]" in message field).
|
|
99
|
-
*/
|
|
100
|
-
static getMessageText(
|
|
101
|
-
type: string,
|
|
102
|
-
customMessages: Record<string, LocaleMessage> = {},
|
|
103
|
-
locale: string | null = null
|
|
104
|
-
): string {
|
|
105
|
-
const resolved = this.getMessage(type, customMessages, locale)
|
|
106
|
-
return typeof resolved === 'string' ? resolved : resolved.message
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get raw message config (used by I18nError; may include a numeric code).
|
|
111
|
-
*/
|
|
112
|
-
static getMessageConfig(
|
|
113
|
-
type: string,
|
|
114
|
-
customMessages: Record<string, LocaleMessage> = {},
|
|
115
|
-
locale: string | null = null
|
|
116
|
-
): LocaleMessage {
|
|
117
|
-
return this._resolveMessage(type, customMessages, locale) ?? { code: type, message: type }
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get the full message table for the given locale (built-in + custom).
|
|
122
|
-
*/
|
|
123
|
-
static getMessages(locale?: string): Record<string, LocaleMessage> {
|
|
124
|
-
const targetLocale = locale ?? this._currentLocale
|
|
125
|
-
const builtinMessages = getMessages(targetLocale) as Record<string, LocaleMessage>
|
|
126
|
-
// Merge custom messages added via addLocale/setMessages for this locale
|
|
127
|
-
const customForLocale: Record<string, LocaleMessage> = {}
|
|
128
|
-
for (const [k, v] of Object.entries(this._customMessages)) {
|
|
129
|
-
if (k.startsWith(`${targetLocale}:`)) {
|
|
130
|
-
customForLocale[k.slice(targetLocale.length + 1)] = v
|
|
131
|
-
} else if (!k.includes(':')) {
|
|
132
|
-
// Global custom messages (no locale prefix)
|
|
133
|
-
customForLocale[k] = v
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return { ...builtinMessages, ...customForLocale }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Reset to defaults (for testing).
|
|
141
|
-
*/
|
|
142
|
-
static reset(): void {
|
|
143
|
-
this._currentLocale = DEFAULT_LOCALE
|
|
144
|
-
this._customMessages = {}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ─── Private Helpers ──────────────────────────────────────────────────────
|
|
148
|
-
|
|
149
|
-
private static _normalizeResolvedMessage(type: string, msg: LocaleMessage): LocaleResolvedMessage {
|
|
150
|
-
if (typeof msg === 'string') {
|
|
151
|
-
return { code: type, message: msg }
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
code: msg.code ?? type,
|
|
155
|
-
message: msg.message,
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private static _resolveMessage(
|
|
160
|
-
type: string,
|
|
161
|
-
customMessages: Record<string, LocaleMessage>,
|
|
162
|
-
locale: string | null
|
|
163
|
-
): LocaleMessage | null {
|
|
164
|
-
const targetLocale = locale ?? this._currentLocale
|
|
165
|
-
|
|
166
|
-
const callerMsg = customMessages[type]
|
|
167
|
-
if (callerMsg !== undefined) return callerMsg
|
|
168
|
-
|
|
169
|
-
const globalMsg = this._customMessages[type]
|
|
170
|
-
if (globalMsg !== undefined) return globalMsg
|
|
171
|
-
|
|
172
|
-
const globalLocaleMsg = this._customMessages[`${targetLocale}:${type}`]
|
|
173
|
-
if (globalLocaleMsg !== undefined) return globalLocaleMsg
|
|
174
|
-
|
|
175
|
-
if (this._isLocaleKey(type)) {
|
|
176
|
-
return getMessage(type as LocaleKey, targetLocale)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return null
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private static _isLocaleKey(key: string): boolean {
|
|
183
|
-
// All predefined locale keys are defined in language pack files
|
|
184
|
-
const msgs = getMessages()
|
|
185
|
-
return key in msgs
|
|
186
|
-
}
|
|
187
|
-
}
|
|
1
|
+
import type { LocaleKey, LocaleMessage } from '../locales/types.js'
|
|
2
|
+
import { getMessage, getMessages, isSupportedLocale, getSupportedLocales } from '../locales/index.js'
|
|
3
|
+
|
|
4
|
+
export interface LocaleResolvedMessage {
|
|
5
|
+
code: string | number
|
|
6
|
+
message: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Locale — global locale manager (static class).
|
|
11
|
+
*
|
|
12
|
+
* v1 compatibility semantics:
|
|
13
|
+
* - getMessage() → returns { code, message } for every resolved message
|
|
14
|
+
* - getMessageText() → always returns final message text (used internally by v2)
|
|
15
|
+
* - getMessageConfig() → returns raw LocaleMessage (may contain code object; used by I18nError)
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_LOCALE = 'en-US'
|
|
18
|
+
|
|
19
|
+
export class Locale {
|
|
20
|
+
private static _currentLocale: string = DEFAULT_LOCALE
|
|
21
|
+
private static _customMessages: Record<string, LocaleMessage> = {}
|
|
22
|
+
|
|
23
|
+
/** v1 compat: expose custom messages */
|
|
24
|
+
static get customMessages(): Record<string, LocaleMessage> {
|
|
25
|
+
return this._customMessages
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** v1 compat: expose all locales as { locale: messages } map */
|
|
29
|
+
static get locales(): Record<string, Record<string, LocaleMessage>> {
|
|
30
|
+
const result: Record<string, Record<string, LocaleMessage>> = {}
|
|
31
|
+
// Built-in locales
|
|
32
|
+
for (const locale of getSupportedLocales()) {
|
|
33
|
+
result[locale] = getMessages(locale) as Record<string, LocaleMessage>
|
|
34
|
+
}
|
|
35
|
+
// Custom locales added via addLocale
|
|
36
|
+
for (const key of Object.keys(this._customMessages)) {
|
|
37
|
+
if (key.includes(':')) {
|
|
38
|
+
const colonIdx = key.indexOf(':')
|
|
39
|
+
const locale = key.substring(0, colonIdx)
|
|
40
|
+
const msgKey = key.substring(colonIdx + 1)
|
|
41
|
+
if (!result[locale]) result[locale] = {}
|
|
42
|
+
result[locale][msgKey] = this._customMessages[key]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Locale Switching ─────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
static setLocale(locale: string): void {
|
|
51
|
+
this._currentLocale = locale
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static getLocale(): string {
|
|
55
|
+
return this._currentLocale
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Custom Messages (global override) ───────────────────────────────────
|
|
59
|
+
|
|
60
|
+
static setMessages(messages: Record<string, LocaleMessage>): void {
|
|
61
|
+
this._customMessages = { ...this._customMessages, ...messages }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static addLocale(locale: string, messages: Record<string, LocaleMessage>): void {
|
|
65
|
+
// Dynamically add a locale pack at runtime (merged into existing entries).
|
|
66
|
+
// Records into customMessages and takes priority during lookup.
|
|
67
|
+
for (const [k, v] of Object.entries(messages)) {
|
|
68
|
+
this._customMessages[`${locale}:${k}`] = v
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static getAvailableLocales(): string[] {
|
|
73
|
+
return getSupportedLocales()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static isSupportedLocale(locale: string): boolean {
|
|
77
|
+
return isSupportedLocale(locale)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Core Query Methods ───────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get a resolved message (v1 compat: returns { code, message } on hit).
|
|
84
|
+
*
|
|
85
|
+
* Priority: custom messages > locale pack > key itself.
|
|
86
|
+
*/
|
|
87
|
+
static getMessage(
|
|
88
|
+
type: string,
|
|
89
|
+
customMessages: Record<string, LocaleMessage> = {},
|
|
90
|
+
locale: string | null = null
|
|
91
|
+
): LocaleResolvedMessage | string {
|
|
92
|
+
const resolved = this._resolveMessage(type, customMessages, locale)
|
|
93
|
+
if (!resolved) return type
|
|
94
|
+
return this._normalizeResolvedMessage(type, resolved)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the final message text (used internally by v2 to avoid "[object Object]" in message field).
|
|
99
|
+
*/
|
|
100
|
+
static getMessageText(
|
|
101
|
+
type: string,
|
|
102
|
+
customMessages: Record<string, LocaleMessage> = {},
|
|
103
|
+
locale: string | null = null
|
|
104
|
+
): string {
|
|
105
|
+
const resolved = this.getMessage(type, customMessages, locale)
|
|
106
|
+
return typeof resolved === 'string' ? resolved : resolved.message
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get raw message config (used by I18nError; may include a numeric code).
|
|
111
|
+
*/
|
|
112
|
+
static getMessageConfig(
|
|
113
|
+
type: string,
|
|
114
|
+
customMessages: Record<string, LocaleMessage> = {},
|
|
115
|
+
locale: string | null = null
|
|
116
|
+
): LocaleMessage {
|
|
117
|
+
return this._resolveMessage(type, customMessages, locale) ?? { code: type, message: type }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the full message table for the given locale (built-in + custom).
|
|
122
|
+
*/
|
|
123
|
+
static getMessages(locale?: string): Record<string, LocaleMessage> {
|
|
124
|
+
const targetLocale = locale ?? this._currentLocale
|
|
125
|
+
const builtinMessages = getMessages(targetLocale) as Record<string, LocaleMessage>
|
|
126
|
+
// Merge custom messages added via addLocale/setMessages for this locale
|
|
127
|
+
const customForLocale: Record<string, LocaleMessage> = {}
|
|
128
|
+
for (const [k, v] of Object.entries(this._customMessages)) {
|
|
129
|
+
if (k.startsWith(`${targetLocale}:`)) {
|
|
130
|
+
customForLocale[k.slice(targetLocale.length + 1)] = v
|
|
131
|
+
} else if (!k.includes(':')) {
|
|
132
|
+
// Global custom messages (no locale prefix)
|
|
133
|
+
customForLocale[k] = v
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { ...builtinMessages, ...customForLocale }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Reset to defaults (for testing).
|
|
141
|
+
*/
|
|
142
|
+
static reset(): void {
|
|
143
|
+
this._currentLocale = DEFAULT_LOCALE
|
|
144
|
+
this._customMessages = {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Private Helpers ──────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
private static _normalizeResolvedMessage(type: string, msg: LocaleMessage): LocaleResolvedMessage {
|
|
150
|
+
if (typeof msg === 'string') {
|
|
151
|
+
return { code: type, message: msg }
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
code: msg.code ?? type,
|
|
155
|
+
message: msg.message,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private static _resolveMessage(
|
|
160
|
+
type: string,
|
|
161
|
+
customMessages: Record<string, LocaleMessage>,
|
|
162
|
+
locale: string | null
|
|
163
|
+
): LocaleMessage | null {
|
|
164
|
+
const targetLocale = locale ?? this._currentLocale
|
|
165
|
+
|
|
166
|
+
const callerMsg = customMessages[type]
|
|
167
|
+
if (callerMsg !== undefined) return callerMsg
|
|
168
|
+
|
|
169
|
+
const globalMsg = this._customMessages[type]
|
|
170
|
+
if (globalMsg !== undefined) return globalMsg
|
|
171
|
+
|
|
172
|
+
const globalLocaleMsg = this._customMessages[`${targetLocale}:${type}`]
|
|
173
|
+
if (globalLocaleMsg !== undefined) return globalLocaleMsg
|
|
174
|
+
|
|
175
|
+
if (this._isLocaleKey(type)) {
|
|
176
|
+
return getMessage(type as LocaleKey, targetLocale)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private static _isLocaleKey(key: string): boolean {
|
|
183
|
+
// All predefined locale keys are defined in language pack files
|
|
184
|
+
const msgs = getMessages()
|
|
185
|
+
return key in msgs
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { renderTemplate } from './TemplateEngine.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MessageTemplate — wraps a template string for rendering.
|
|
5
|
-
* Delegates to TemplateEngine.renderTemplate() (fix CORE-03).
|
|
6
|
-
* Maintains full v1 API compatibility (constructor + render + static render + static renderBatch).
|
|
7
|
-
*/
|
|
8
|
-
export class MessageTemplate {
|
|
9
|
-
private readonly template: string
|
|
10
|
-
|
|
11
|
-
constructor(template: string) {
|
|
12
|
-
this.template = template
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Render the template with the given context.
|
|
17
|
-
*/
|
|
18
|
-
render(context: Record<string, unknown> = {}): string {
|
|
19
|
-
return renderTemplate(this.template, context)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Statically render a template string with the given context.
|
|
24
|
-
*/
|
|
25
|
-
static render(template: string, context: Record<string, unknown> = {}): string {
|
|
26
|
-
return renderTemplate(template, context)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Statically render multiple templates in batch.
|
|
31
|
-
*/
|
|
32
|
-
static renderBatch(
|
|
33
|
-
templates: Record<string, string>,
|
|
34
|
-
context: Record<string, unknown> = {}
|
|
35
|
-
): Record<string, string> {
|
|
36
|
-
const result: Record<string, string> = {}
|
|
37
|
-
for (const [key, tmpl] of Object.entries(templates)) {
|
|
38
|
-
result[key] = renderTemplate(tmpl, context)
|
|
39
|
-
}
|
|
40
|
-
return result
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
import { renderTemplate } from './TemplateEngine.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MessageTemplate — wraps a template string for rendering.
|
|
5
|
+
* Delegates to TemplateEngine.renderTemplate() (fix CORE-03).
|
|
6
|
+
* Maintains full v1 API compatibility (constructor + render + static render + static renderBatch).
|
|
7
|
+
*/
|
|
8
|
+
export class MessageTemplate {
|
|
9
|
+
private readonly template: string
|
|
10
|
+
|
|
11
|
+
constructor(template: string) {
|
|
12
|
+
this.template = template
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render the template with the given context.
|
|
17
|
+
*/
|
|
18
|
+
render(context: Record<string, unknown> = {}): string {
|
|
19
|
+
return renderTemplate(this.template, context)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Statically render a template string with the given context.
|
|
24
|
+
*/
|
|
25
|
+
static render(template: string, context: Record<string, unknown> = {}): string {
|
|
26
|
+
return renderTemplate(template, context)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Statically render multiple templates in batch.
|
|
31
|
+
*/
|
|
32
|
+
static renderBatch(
|
|
33
|
+
templates: Record<string, string>,
|
|
34
|
+
context: Record<string, unknown> = {}
|
|
35
|
+
): Record<string, string> {
|
|
36
|
+
const result: Record<string, string> = {}
|
|
37
|
+
for (const [key, tmpl] of Object.entries(templates)) {
|
|
38
|
+
result[key] = renderTemplate(tmpl, context)
|
|
39
|
+
}
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
}
|