cabloy 5.1.235 → 5.1.238
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/.cabloy-version +1 -1
- package/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/repo-docs/backend/broadcast-guide.md +2 -0
- package/repo-docs/backend/queue-guide.md +31 -0
- package/repo-docs/backend/schedule-guide.md +23 -0
- package/repo-docs/frontend/scripts.md +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/resource/locale/appLocale.ts +2 -2
- package/vona/packages-vona/vona-core/src/lib/bean/resource/locale/utils.ts +46 -1
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +4 -0
- package/vona/packages-vona/vona-core/src/lib/module/locales.ts +41 -22
- package/vona/packages-vona/vona-core/src/lib/utils/zod-enhance.ts +1 -1
- package/vona/packages-vona/vona-core/src/types/config/config.ts +2 -0
- package/vona/packages-vona/vona-core/src/types/config/locale.ts +11 -4
- package/vona/packages-vona/vona-core/src/types/interface/module.ts +1 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/src/backend/config/config/config.ts +9 -0
- package/vona/src/suite/a-home/modules/home-base/src/main.ts +1 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-hmr/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-hmr/src/service/watch.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-locale/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-locale/src/config/config.ts +2 -4
- package/vona/src/suite-vendor/a-vona/modules/a-locale/src/main.ts +12 -30
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/bean/bean.openapi.ts +6 -2
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/service/openapi.ts +107 -48
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/contract.ts +25 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/const/decorator.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/decorator/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/decorator/mcp.ts +28 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/types/api.ts +8 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/src/config/config.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/service/rateLimit.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/types/rateLimit.ts +4 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/core/component/locale.ts +13 -3
- package/zova/packages-zova/zova-core/src/core/sys/config.ts +28 -1
- package/zova/packages-zova/zova-core/src/core/sys/locale.ts +24 -0
- package/zova/packages-zova/zova-core/src/core/sys/module.ts +16 -4
- package/zova/packages-zova/zova-core/src/core/sys/sys.ts +7 -2
- package/zova/packages-zova/zova-core/src/types/interface/module.ts +1 -0
- package/zova/packages-zova/zova-core/src/utils/zod-enhance.ts +1 -1
- package/zova/pnpm-lock.yaml +7 -7
- package/zova/quasar.config.ts +2 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +1 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +1 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.locale.tsx +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +13 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssrNavigation.ts +24 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +8 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrNavigation.test.ts +54 -1
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.238
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -8,6 +8,8 @@ Broadcast lets one worker emit a message that multiple worker processes can rece
|
|
|
8
8
|
|
|
9
9
|
This is important because some business logic is not point-to-point background work. It is multi-worker coordination work.
|
|
10
10
|
|
|
11
|
+
The current broadcast transport uses nonpersistent Redis Pub/Sub, not BullMQ jobs. It has no retained job history or queue-style retention options such as `removeOnComplete` / `removeOnFail`.
|
|
12
|
+
|
|
11
13
|
## Create a broadcast
|
|
12
14
|
|
|
13
15
|
Example: create a broadcast named `echo` in module `training-student`.
|
|
@@ -188,6 +188,37 @@ class QueueAdd {}
|
|
|
188
188
|
|
|
189
189
|
That means queue behavior is not only asynchronous. It also participates in concurrency, isolation, and consistency policy.
|
|
190
190
|
|
|
191
|
+
### Finished-job retention
|
|
192
|
+
|
|
193
|
+
The global `a-queue` worker defaults are `removeOnComplete: { count: 0 }` and `removeOnFail: { count: 0 }`. Jobs are removed immediately on success or final failure, not on intermediate failures that will be retried. This removes finished job records, not the BullMQ events stream: `pushAsync` still receives completion results and final errors through `QueueEvents`.
|
|
194
|
+
|
|
195
|
+
For queue-specific retention, set `options.worker` explicitly. Worker removal options take BullMQ `KeepJobs` objects, not booleans; for example, retain up to 100 completed and 100 failed jobs:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
@Queue({
|
|
199
|
+
options: {
|
|
200
|
+
worker: {
|
|
201
|
+
removeOnComplete: { count: 100 },
|
|
202
|
+
removeOnFail: { count: 100 },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
class QueueAdd {}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For ordinary `push` / `pushAsync` jobs, `options.job` can instead set `removeOnComplete` / `removeOnFail` to `false` (retain without automatic removal) or a bounded object such as `{ count: 100 }`. Job-level values override worker defaults; per-push `jobOptions` override the corresponding `options.job` values:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
await this.scope.queue.add.push(data, {
|
|
213
|
+
jobOptions: {
|
|
214
|
+
removeOnComplete: { count: 100 },
|
|
215
|
+
removeOnFail: false,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Changing the defaults does not automatically purge previously retained jobs. Retain records when later job lookups or manual retries are needed. Custom `jobId` duplicate suppression lasts only while that job exists; after removal, the same ID can be submitted again. Bounded retention is not an exact TTL or a permanent deduplication guarantee.
|
|
221
|
+
|
|
191
222
|
## Configure queues in app config
|
|
192
223
|
|
|
193
224
|
Queue options can also be overridden in app config.
|
|
@@ -57,6 +57,29 @@ Schedule parameters can also include:
|
|
|
57
57
|
|
|
58
58
|
This matters because recurring jobs often have the same consistency concerns as ordinary queue jobs.
|
|
59
59
|
|
|
60
|
+
### Finished-run retention
|
|
61
|
+
|
|
62
|
+
Scheduled runs inherit the selected queue's worker policy, including the global `a-queue` defaults: remove immediately on success or final failure, but not during intermediate retries. See [Finished-job retention](/backend/queue-guide#finished-job-retention) for worker overrides and operational caveats.
|
|
63
|
+
|
|
64
|
+
Schedules call BullMQ `upsertJobScheduler` directly, bypassing the normal queue-push `options.job` merge. To override retention for a schedule, use `templateOptions`, for example to retain up to 100 successful and 100 failed runs:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
@Schedule({
|
|
68
|
+
repeat: { every: 3000 },
|
|
69
|
+
templateOptions: {
|
|
70
|
+
removeOnComplete: { count: 100 },
|
|
71
|
+
removeOnFail: { count: 100 },
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
export class ScheduleLog extends BeanBase implements IScheduleExecute {
|
|
75
|
+
async execute() {
|
|
76
|
+
console.log(Date.now());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
These options govern finished runs, not the recurring scheduler definition; changing them does not automatically purge old retained runs.
|
|
82
|
+
|
|
60
83
|
## Enable/disable and environment scoping
|
|
61
84
|
|
|
62
85
|
Like queues, schedules can be enabled or disabled and scoped to flavor or mode.
|
|
@@ -53,7 +53,7 @@ The `dev:zova:*` commands start the Zova development server, which uses `9000` i
|
|
|
53
53
|
|
|
54
54
|
Cabloy Basic and Cabloy Start enable `code-inspector-plugin` in Zova development builds. In the active edition's repository, start either `npm run dev:zova:admin` or `npm run dev:zova:web`, then open the corresponding Zova standalone SSR page. The wrappers select `cabloyBasicAdmin` / `cabloyBasicWeb` in Basic and `cabloyStartAdmin` / `cabloyStartWeb` in Start. Admin and Web share the worktree's development ports; run them sequentially, not concurrently.
|
|
55
55
|
|
|
56
|
-
Hold **Option+Shift** on macOS or **Alt+Shift** on Windows/Linux, move over an element, and click to open its authored TSX location. This works with TSX in Controllers and split Render beans; it does not require converting them into Vue SFCs. Fragments have no independent DOM node, and
|
|
56
|
+
Hold **Option+Shift** on macOS or **Alt+Shift** on Windows/Linux, move over an element, and click to open its authored TSX location. This works with TSX in Controllers and split Render beans; it does not require converting them into Vue SFCs. Fragments have no independent DOM node. In both editions, instrumentation skips PascalCase and member-expression component tags and retains their native TSX descendants. This avoids passing inspector attributes to wrappers such as `ClientOnly` and `VDefaultsProvider` that have no inheritable DOM root; component invocation tags are not direct location targets.
|
|
57
57
|
|
|
58
58
|
The application configuration in `zova/quasar.config.ts` uses the browser URI handler `vscode://file/{file}:{line}:{column}`. VS Code must be installed and registered for that scheme; the browser may ask for permission to open it. To use another compatible editor, change `behavior.target` to its supported URI scheme. Actual editor opening depends on that local browser/OS setup.
|
|
59
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.118",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|
|
@@ -5,11 +5,11 @@ import type { ILocaleRecord, IModuleLocale, IModuleLocaleText } from './type.ts'
|
|
|
5
5
|
import { cast } from '../../../../types/utils/cast.ts';
|
|
6
6
|
import { BeanSimple } from '../../beanSimple.ts';
|
|
7
7
|
import { LocaleModuleNameSeparator } from './type.ts';
|
|
8
|
-
import { isLocaleMagic } from './utils.ts';
|
|
8
|
+
import { isLocaleMagic, resolveLocale } from './utils.ts';
|
|
9
9
|
|
|
10
10
|
export class AppLocale extends BeanSimple {
|
|
11
11
|
get current(): keyof ILocaleRecord {
|
|
12
|
-
return this.ctx ? this.ctx.locale :
|
|
12
|
+
return this.ctx ? this.ctx.locale : resolveLocale(undefined, this.app.config.locale);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
set current(value: keyof ILocaleRecord) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { ConfigLocale } from '../../../../types/config/locale.ts';
|
|
1
2
|
import type { IBeanScopeLocale } from '../../type.ts';
|
|
2
|
-
import type { ILocaleMagic } from './type.ts';
|
|
3
|
+
import type { ILocaleMagic, ILocaleRecord } from './type.ts';
|
|
3
4
|
|
|
4
5
|
import { cast } from '../../../../types/utils/cast.ts';
|
|
5
6
|
import { useApp } from '../../../framework/useApp.ts';
|
|
@@ -59,3 +60,47 @@ export function text(strings: any, ...expressions: any): any {
|
|
|
59
60
|
export function isLocaleMagic(str: string | object) {
|
|
60
61
|
return typeof str === 'object' && cast(str).toJSON;
|
|
61
62
|
}
|
|
63
|
+
|
|
64
|
+
export function canonicalizeLocale(value: unknown): string {
|
|
65
|
+
return typeof value === 'string' ? value.trim().replaceAll('_', '-').toLowerCase() : '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function validateLocaleConfig(config: ConfigLocale): void {
|
|
69
|
+
const items = config?.items;
|
|
70
|
+
if (!items || typeof items !== 'object' || Object.keys(items).length === 0) {
|
|
71
|
+
throw new Error('config.locale.items must contain at least one enabled locale');
|
|
72
|
+
}
|
|
73
|
+
for (const key of Object.keys(items)) {
|
|
74
|
+
if (!key || canonicalizeLocale(key) !== key || typeof items[key] !== 'string') {
|
|
75
|
+
throw new Error(`config.locale.items contains an invalid locale: ${key}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
typeof config.default !== 'string' ||
|
|
80
|
+
!Object.hasOwn(items, config.default) ||
|
|
81
|
+
canonicalizeLocale(config.default) !== config.default
|
|
82
|
+
) {
|
|
83
|
+
throw new Error('config.locale.default must be an enabled config.locale.items key');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Resolve an enabled input without allowing aliases to expand the configured set. */
|
|
88
|
+
export function resolveEnabledLocale(
|
|
89
|
+
value: unknown,
|
|
90
|
+
config: ConfigLocale,
|
|
91
|
+
aliases: Record<string, string> = {},
|
|
92
|
+
): keyof ILocaleRecord | undefined {
|
|
93
|
+
validateLocaleConfig(config);
|
|
94
|
+
let locale = canonicalizeLocale(value);
|
|
95
|
+
if (Object.hasOwn(aliases, locale)) locale = canonicalizeLocale(aliases[locale]);
|
|
96
|
+
return Object.hasOwn(config.items, locale) ? (locale as keyof ILocaleRecord) : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Active-language selection only; explicit translation lookup remains independent. */
|
|
100
|
+
export function resolveLocale(
|
|
101
|
+
value: unknown,
|
|
102
|
+
config: ConfigLocale,
|
|
103
|
+
aliases?: Record<string, string>,
|
|
104
|
+
): keyof ILocaleRecord {
|
|
105
|
+
return resolveEnabledLocale(value, config, aliases) ?? config.default;
|
|
106
|
+
}
|
|
@@ -97,6 +97,10 @@ async function __prepareConfig(app: VonaApplication): Promise<VonaConfigOptional
|
|
|
97
97
|
const configItemsRes = await Promise.all(configItemsPromise);
|
|
98
98
|
for (const configItem of configItemsRes) {
|
|
99
99
|
if (configItem) {
|
|
100
|
+
// An explicit language subset replaces the preceding policy, not its individual keys.
|
|
101
|
+
if (configItem.locale && Object.hasOwn(configItem.locale, 'items') && config.locale) {
|
|
102
|
+
delete config.locale.items;
|
|
103
|
+
}
|
|
100
104
|
deepExtend(config, configItem);
|
|
101
105
|
}
|
|
102
106
|
}
|
|
@@ -1,34 +1,56 @@
|
|
|
1
1
|
import type { IModule } from '@cabloy/module-info';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ILocaleProviderResource,
|
|
5
|
+
TypeModuleResourceLocales,
|
|
6
|
+
VonaLocaleOptional,
|
|
7
|
+
} from '../../types/index.ts';
|
|
4
8
|
import type { VonaApplication } from '../core/application.ts';
|
|
5
9
|
|
|
6
10
|
import localesDefault from '../core/locales.ts';
|
|
7
11
|
import { deepExtend } from '../utils/util.ts';
|
|
8
12
|
|
|
9
13
|
export default async function (app: VonaApplication, modules: Record<string, IModule>) {
|
|
10
|
-
//
|
|
11
|
-
app.meta.locales = localesDefault;
|
|
14
|
+
// Each application owns its registry; never add provider/project data to imported resources.
|
|
15
|
+
app.meta.locales = deepExtend({}, localesDefault);
|
|
12
16
|
app.meta.localeModules = {};
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
const localesAll = await app.options.locales();
|
|
19
|
+
// Preserve existing project global/module override semantics.
|
|
20
|
+
for (const locale in localesAll.localesDefault) {
|
|
21
|
+
_initLocales(locale, localesAll.localesDefault[locale]);
|
|
22
|
+
}
|
|
23
|
+
for (const locale in localesAll.localesModules) {
|
|
24
|
+
_initLocales(locale, localesAll.localesModules[locale]);
|
|
25
|
+
}
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
// Providers precede all ordinary dictionaries, independent of their module order.
|
|
28
|
+
for (const moduleName of Object.keys(modules)) {
|
|
29
|
+
const module = modules[moduleName];
|
|
30
|
+
if (!module.info.capabilities?.locale) continue;
|
|
31
|
+
const locales = module.resource.localesProvider;
|
|
32
|
+
if (!locales) continue;
|
|
33
|
+
for (const locale of Object.keys(locales)) {
|
|
34
|
+
const resource = locales[locale] as ILocaleProviderResource;
|
|
35
|
+
const globals = Object.fromEntries(
|
|
36
|
+
Object.entries(resource).filter(
|
|
37
|
+
([key, value]) => key !== 'modules' && typeof value === 'string',
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
app.meta.locales[locale] = Object.assign({}, globals, app.meta.locales[locale]);
|
|
41
|
+
for (const target of Object.keys(resource.modules ?? {})) {
|
|
42
|
+
// Metadata membership is sufficient; no target import, install or dependency is needed.
|
|
43
|
+
if (!Object.hasOwn(modules, target)) continue;
|
|
44
|
+
_registerLocale(target, locale, resource.modules![target]);
|
|
45
|
+
}
|
|
26
46
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Preserve project/provider precedence when ordinary target dictionaries are reloaded.
|
|
50
|
+
app.meta.hmrCacheLocaleModules = deepExtend({}, app.meta.localeModules);
|
|
51
|
+
for (const moduleName of Object.keys(modules)) {
|
|
52
|
+
const module = modules[moduleName];
|
|
53
|
+
if (!module.info.capabilities?.locale) {
|
|
32
54
|
_registerLocales(moduleName, module.resource.locales);
|
|
33
55
|
}
|
|
34
56
|
}
|
|
@@ -36,7 +58,6 @@ export default async function (app: VonaApplication, modules: Record<string, IMo
|
|
|
36
58
|
function _initLocales(locale: string, locales: VonaLocaleOptional | undefined) {
|
|
37
59
|
if (!locales) return;
|
|
38
60
|
if (!locales.modules) {
|
|
39
|
-
// override
|
|
40
61
|
app.meta.locales[locale] = Object.assign({}, app.meta.locales[locale], locales);
|
|
41
62
|
} else {
|
|
42
63
|
const moduleMap = locales.modules;
|
|
@@ -54,9 +75,7 @@ export default async function (app: VonaApplication, modules: Record<string, IMo
|
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
function _registerLocale(moduleName: string, locale: string, moduleLocales: object) {
|
|
57
|
-
// locales: not override
|
|
58
78
|
app.meta.locales[locale] = Object.assign({}, moduleLocales, app.meta.locales[locale]);
|
|
59
|
-
// localeModules
|
|
60
79
|
if (!app.meta.localeModules[moduleName]) app.meta.localeModules[moduleName] = {};
|
|
61
80
|
app.meta.localeModules[moduleName][locale] = Object.assign(
|
|
62
81
|
{},
|
|
@@ -11,7 +11,7 @@ import type { VonaApplication } from '../core/application.ts';
|
|
|
11
11
|
import { useApp } from '../framework/useApp.ts';
|
|
12
12
|
|
|
13
13
|
export type ZodLocaleError = () => { localeError: z.core.$ZodErrorMap };
|
|
14
|
-
export type ZodLocaleErrors = Record<keyof ILocaleRecord, ZodLocaleError
|
|
14
|
+
export type ZodLocaleErrors = Partial<Record<keyof ILocaleRecord, ZodLocaleError>>;
|
|
15
15
|
|
|
16
16
|
const queryCoercion = new AsyncLocalStorage<boolean>();
|
|
17
17
|
|
|
@@ -2,6 +2,7 @@ import type { VonaConfigMeta } from '@cabloy/module-info';
|
|
|
2
2
|
|
|
3
3
|
import type { ConfigLogger, IBeanScopeConfig } from '../../index.ts';
|
|
4
4
|
import type { PowerPartial } from '../utils/powerPartial.ts';
|
|
5
|
+
import type { ConfigLocale } from './locale.ts';
|
|
5
6
|
|
|
6
7
|
// @ts-ignore ignore the throw type check of 'development'
|
|
7
8
|
export interface VonaConfig {
|
|
@@ -31,6 +32,7 @@ export interface VonaConfig {
|
|
|
31
32
|
maxIpsCount: number;
|
|
32
33
|
};
|
|
33
34
|
logger: ConfigLogger;
|
|
35
|
+
locale: ConfigLocale;
|
|
34
36
|
//
|
|
35
37
|
modules: IBeanScopeConfig;
|
|
36
38
|
}
|
|
@@ -2,13 +2,20 @@ import type { ILocaleRecord } from '../../lib/bean/resource/locale/type.ts';
|
|
|
2
2
|
import type { IBeanScopeLocale } from '../../lib/bean/type.ts';
|
|
3
3
|
import type { PowerPartial } from '../utils/powerPartial.ts';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export interface ConfigLocale {
|
|
6
|
+
items: Partial<Record<keyof ILocaleRecord, string>>;
|
|
7
|
+
default: keyof ILocaleRecord;
|
|
8
|
+
}
|
|
8
9
|
|
|
9
10
|
export interface VonaLocale {
|
|
10
11
|
modules: IBeanScopeLocale;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export type VonaLocaleOptional = PowerPartial<VonaLocale>;
|
|
14
|
-
export type VonaLocaleOptionalMap = Record<keyof ILocaleRecord, VonaLocaleOptional
|
|
15
|
+
export type VonaLocaleOptionalMap = Partial<Record<keyof ILocaleRecord, VonaLocaleOptional>>;
|
|
16
|
+
|
|
17
|
+
// Only capability.locale modules interpret this envelope as cross-module resources.
|
|
18
|
+
export interface ILocaleProviderResource {
|
|
19
|
+
modules?: Record<string, Record<string, string>>;
|
|
20
|
+
[key: string]: string | Record<string, Record<string, string>> | undefined;
|
|
21
|
+
}
|
|
@@ -17,6 +17,7 @@ export interface IModuleResource {
|
|
|
17
17
|
Main: new () => IModuleMain;
|
|
18
18
|
Monkey: new () => IMonkeyModule & IMonkeySystem;
|
|
19
19
|
locales: TypeModuleResourceLocales;
|
|
20
|
+
localesProvider: TypeModuleResourceLocales;
|
|
20
21
|
errors: TypeModuleResourceErrors;
|
|
21
22
|
config: TypeModuleResourceConfig;
|
|
22
23
|
constants: unknown;
|
|
@@ -39,6 +39,15 @@ export default async function (app: VonaApplication) {
|
|
|
39
39
|
mode: env.META_MODE,
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
// Language availability is application policy, independent of installed dictionaries.
|
|
43
|
+
config.locale = {
|
|
44
|
+
items: {
|
|
45
|
+
'en-us': 'LanguageEnglish',
|
|
46
|
+
'zh-cn': 'LanguageChinese',
|
|
47
|
+
},
|
|
48
|
+
default: 'en-us',
|
|
49
|
+
};
|
|
50
|
+
|
|
42
51
|
// instance
|
|
43
52
|
config.instance = {
|
|
44
53
|
getInstanceName: undefined,
|
|
@@ -10,10 +10,7 @@ export class Main extends BeanSimple implements IModuleMain {
|
|
|
10
10
|
'en-us': en,
|
|
11
11
|
'zh-cn': zhCN,
|
|
12
12
|
};
|
|
13
|
-
this.app.util.setLocaleErrors(
|
|
14
|
-
localeErrors,
|
|
15
|
-
this.app.scope('a-locale').config.locale.defaultLocale,
|
|
16
|
-
);
|
|
13
|
+
this.app.util.setLocaleErrors(localeErrors, this.app.config.locale.default);
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
async configLoaded(_config: any) {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-suite-a-cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.69",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"vona-module-a-cabloy": "workspace:^",
|
|
11
11
|
"vona-module-a-layoutprofile": "^5.0.2",
|
|
12
12
|
"vona-module-a-markdown": "^5.0.3",
|
|
13
|
-
"vona-module-a-rbac": "^5.0.
|
|
13
|
+
"vona-module-a-rbac": "^5.0.17",
|
|
14
14
|
"vona-module-a-socket": "workspace:^",
|
|
15
15
|
"vona-module-a-ssr": "workspace:^",
|
|
16
16
|
"vona-module-a-ssrhmr": "workspace:^"
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IProtocolKeyRecord, VonaApplication } from 'vona';
|
|
2
2
|
|
|
3
3
|
import { $protocolKey } from 'vona';
|
|
4
4
|
|
|
5
5
|
export interface I18nConfigLocale {
|
|
6
|
-
defaultLocale: keyof ILocaleRecord;
|
|
7
6
|
queryField?: keyof IProtocolKeyRecord;
|
|
8
7
|
headerField?: keyof IProtocolKeyRecord;
|
|
9
8
|
cookieField?: string;
|
|
10
|
-
localeAlias:
|
|
9
|
+
localeAlias: Record<string, string>;
|
|
11
10
|
writeCookie: boolean;
|
|
12
11
|
cookieMaxAge: number;
|
|
13
12
|
cookieDomain?: string;
|
|
@@ -31,7 +30,6 @@ export interface I18nConfig {
|
|
|
31
30
|
export function config(_app: VonaApplication) {
|
|
32
31
|
return {
|
|
33
32
|
locale: {
|
|
34
|
-
defaultLocale: 'en-us',
|
|
35
33
|
queryField: $protocolKey('x-vona-locale'),
|
|
36
34
|
headerField: $protocolKey('x-vona-locale'),
|
|
37
35
|
cookieField: 'locale',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IModuleMain, VonaContext } from 'vona';
|
|
2
2
|
|
|
3
|
-
import { BeanSimple } from 'vona';
|
|
3
|
+
import { BeanSimple, resolveEnabledLocale, resolveLocale, validateLocaleConfig } from 'vona';
|
|
4
4
|
|
|
5
5
|
import type { I18nConfigLocale, I18nConfigTz } from './config/config.ts';
|
|
6
6
|
|
|
@@ -15,11 +15,12 @@ export class Main extends BeanSimple implements IModuleMain {
|
|
|
15
15
|
async moduleLoading() {}
|
|
16
16
|
async moduleLoaded() {
|
|
17
17
|
const options = this.app.scope(__ThisModule__).config;
|
|
18
|
+
validateLocaleConfig(this.app.config.locale);
|
|
18
19
|
this.app.context.__getLocale = function (this: VonaContext) {
|
|
19
20
|
return __getLocale(this, options.locale);
|
|
20
21
|
};
|
|
21
22
|
this.app.context.__setLocale = function (this: VonaContext, locale: string) {
|
|
22
|
-
return __setLocale(this, locale);
|
|
23
|
+
return __setLocale(this, locale, options.locale);
|
|
23
24
|
};
|
|
24
25
|
this.app.context.__getTz = function (this: VonaContext) {
|
|
25
26
|
return __getTz(this, options.tz);
|
|
@@ -32,8 +33,8 @@ export class Main extends BeanSimple implements IModuleMain {
|
|
|
32
33
|
async configLoaded(_config: any) {}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
function __setLocale(ctx: VonaContext, locale: string) {
|
|
36
|
-
ctx[SymbolLocale] = locale;
|
|
36
|
+
function __setLocale(ctx: VonaContext, locale: string, options: I18nConfigLocale) {
|
|
37
|
+
ctx[SymbolLocale] = resolveLocale(locale, ctx.app.config.locale, options.localeAlias);
|
|
37
38
|
ctx[SymbolLocaleOrigin] = 'set';
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -42,7 +43,7 @@ function __getLocale(ctx: VonaContext, options: I18nConfigLocale) {
|
|
|
42
43
|
return ctx[SymbolLocale];
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
const
|
|
46
|
+
const policy = ctx.app.config.locale;
|
|
46
47
|
|
|
47
48
|
const queryField = options.queryField;
|
|
48
49
|
const headerField = options.headerField;
|
|
@@ -89,9 +90,10 @@ function __getLocale(ctx: VonaContext, options: I18nConfigLocale) {
|
|
|
89
90
|
}
|
|
90
91
|
if (languages.length > 0) {
|
|
91
92
|
for (let i = 0; i < languages.length; i++) {
|
|
92
|
-
const lang =
|
|
93
|
-
if (
|
|
94
|
-
|
|
93
|
+
const lang = resolveEnabledLocale(languages[i], policy, options.localeAlias);
|
|
94
|
+
if (lang) {
|
|
95
|
+
// Keep the input until final resolution so an alias is applied only once.
|
|
96
|
+
locale = languages[i];
|
|
95
97
|
localeOrigin = 'header';
|
|
96
98
|
break;
|
|
97
99
|
}
|
|
@@ -104,23 +106,8 @@ function __getLocale(ctx: VonaContext, options: I18nConfigLocale) {
|
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
locale = options.defaultLocale;
|
|
110
|
-
localeOrigin = 'default';
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// cookie alias
|
|
114
|
-
if ((locale as string) in options.localeAlias) {
|
|
115
|
-
locale = options.localeAlias[locale as string];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
locale = formatLocale(locale);
|
|
119
|
-
|
|
120
|
-
// validate locale
|
|
121
|
-
if (!localeNames.includes(locale)) {
|
|
122
|
-
locale = options.defaultLocale;
|
|
123
|
-
}
|
|
109
|
+
if (!locale) localeOrigin = 'default';
|
|
110
|
+
locale = resolveLocale(locale, policy, options.localeAlias);
|
|
124
111
|
|
|
125
112
|
// if header not send, set the locale cookie
|
|
126
113
|
if (cookieField && options.writeCookie && cookieLocale !== locale && !ctx.headerSent) {
|
|
@@ -217,11 +204,6 @@ function updateCookie(ctx: VonaContext, options: I18nConfigLocale | I18nConfigTz
|
|
|
217
204
|
ctx.cookies.set(options.cookieField!, locale, cookieOptions);
|
|
218
205
|
}
|
|
219
206
|
|
|
220
|
-
function formatLocale(locale) {
|
|
221
|
-
// support zh_CN, en_US => zh-CN, en-US
|
|
222
|
-
return parseTokenSafe(locale).replace('_', '-').toLowerCase();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
207
|
function parseTokenSafe(token?: string) {
|
|
226
208
|
if (!token) return '';
|
|
227
209
|
return token.replace(/[\\.*#%'"`;, ]/g, '');
|