@warlock.js/core 5.25.0 → 5.26.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/CHANGELOG.md +34 -0
- package/esm/cli/commands/doctor/checks/index.mjs +2 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +5 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +4 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +2 -2
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/dev-server/legacy-locales-warning.mjs +16 -0
- package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
- package/esm/dev-server/module-loader.mjs +16 -0
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/special-file-patterns.mjs +2 -2
- package/esm/dev-server/special-file-patterns.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +7 -3
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +27 -4
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +38 -18
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +2 -2
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/http/build-rate-limit-options.d.mts +8 -2
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs +1 -0
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/errors/errors.d.mts +1 -17
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -20
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +2 -2
- package/esm/http/index.mjs +1 -1
- package/esm/http/middleware/inject-request-context.d.mts +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/request.d.mts +7 -24
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +1 -22
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +4 -2
- package/esm/http/response.mjs.map +1 -1
- package/esm/index.d.mts +5 -3
- package/esm/index.mjs +5 -2
- package/esm/localization/index.mjs +4 -0
- package/esm/localization/parse-locale-dictionary.d.mts +23 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/parse-locale-dictionary.mjs +215 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
- package/esm/localization/register-locale-dictionary.d.mts +12 -0
- package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/register-locale-dictionary.mjs +58 -0
- package/esm/localization/register-locale-dictionary.mjs.map +1 -0
- package/esm/production/production-builder.mjs +11 -2
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/router/types.d.mts +8 -0
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.d.mts +10 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +16 -2
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +48 -1
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +118 -75
- package/llms.txt +1 -1
- package/package.json +20 -20
- package/skills/README.md +1 -1
- package/skills/create-controller/SKILL.md +4 -0
- package/skills/test-http/SKILL.md +9 -1
- package/skills/use-localization/SKILL.md +39 -26
|
@@ -50,7 +50,7 @@ import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests"
|
|
|
50
50
|
- Loads `warlock.config.ts` + bootstrap + `filesOrchestrator` (without file watching).
|
|
51
51
|
- Loads `src/config/*.ts`.
|
|
52
52
|
- Loads every module (`routes.ts`, `main.ts`, `events/*.ts`).
|
|
53
|
-
- Starts **ALL** connectors, including HTTP.
|
|
53
|
+
- Starts **ALL** connectors, including HTTP, by default.
|
|
54
54
|
|
|
55
55
|
Unlike the dev server, it doesn't watch files, doesn't do HMR, doesn't run health checkers. Just a working HTTP endpoint listening on the configured port.
|
|
56
56
|
|
|
@@ -70,6 +70,14 @@ The explicit port wins over `http.port` — including over `HTTP_PORT` in `.env`
|
|
|
70
70
|
|
|
71
71
|
The request helpers follow with no extra wiring: the bound port is published as `WARLOCK_TEST_SERVER_PORT`, which the workers inherit, and `getTestServerUrl()` prefers it over their own `.env`-resolved `http.port`. `stopHttpTestServer()` withdraws it.
|
|
72
72
|
|
|
73
|
+
### API-only suites — `startHttpTestServer({ web: false })`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
await startHttpTestServer({ web: false });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use this in `globalSetup` for a suite that exercises API routes only. It still boots the HTTP connector and every other configured connector, but excludes the optional `web` connector before its lifecycle begins. That means web's lazy delegate never constructs `WebConnector`, and Vite is never created. Omitting `web` keeps the default full-stack behavior.
|
|
80
|
+
|
|
73
81
|
### The port is preflighted
|
|
74
82
|
|
|
75
83
|
Before binding, the test server checks the resolved port is free and fails with an instruction if it isn't:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: use-localization
|
|
3
|
-
description: 'Multi-locale translations via `groupedTranslations` (declare keys), `t()`
|
|
3
|
+
description: 'Multi-locale translations via `groupedTranslations` (declare keys), `t()` (look up; always use it in app code, never `request.t()`), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Warlock — translate keys + pick localized values
|
|
@@ -15,7 +15,7 @@ Both pivot on **the request's locale** — auto-detected from headers / query st
|
|
|
15
15
|
## The shape
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
// 1. Declare keys (auto-loaded from src/app/<module>/utils/locales.
|
|
18
|
+
// 1. Declare keys (auto-loaded from src/app/<module>/utils/locales.json)
|
|
19
19
|
import { groupedTranslations } from "@mongez/localization";
|
|
20
20
|
|
|
21
21
|
groupedTranslations("products", {
|
|
@@ -27,8 +27,7 @@ groupedTranslations("products", {
|
|
|
27
27
|
import { t } from "@warlock.js/core";
|
|
28
28
|
|
|
29
29
|
throw new ResourceNotFoundError(t("products.notFound"));
|
|
30
|
-
|
|
31
|
-
return response.success({ message: request.t("products.created") });
|
|
30
|
+
return response.success({ message: t("products.created") });
|
|
32
31
|
|
|
33
32
|
// 3. Pick a value from a localized-array column
|
|
34
33
|
import { getLocalized } from "@warlock.js/core";
|
|
@@ -37,7 +36,26 @@ const name = getLocalized(product.get("name_translations"));
|
|
|
37
36
|
// → reads the current request's locale, returns the matching value
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
## Declaring translations — `
|
|
39
|
+
## Declaring translations — module `locales.json`
|
|
40
|
+
|
|
41
|
+
Prefer a data-only dictionary in `src/app/<module>/utils/locales.json`. The
|
|
42
|
+
module folder is its default group, so this example registers
|
|
43
|
+
`products.notFound`; a root `$group` string replaces that group when a module
|
|
44
|
+
needs a different namespace. The module loader validates the strict JSON
|
|
45
|
+
shape, flattens nested keys, and rejects duplicate ownership. Do not import the
|
|
46
|
+
file yourself.
|
|
47
|
+
|
|
48
|
+
```json title="src/app/products/utils/locales.json"
|
|
49
|
+
{
|
|
50
|
+
"notFound": { "en": "Product not found", "ar": "المنتج غير موجود" },
|
|
51
|
+
"errors": { "outOfStock": { "en": "Product out of stock", "ar": "المنتج غير متوفر" } }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`utils/locales.ts` with `groupedTranslations` remains supported but is
|
|
56
|
+
deprecated and will be removed in v6. Migrate it with `warlock doctor --fix`.
|
|
57
|
+
|
|
58
|
+
### Legacy `groupedTranslations`
|
|
41
59
|
|
|
42
60
|
Every module owns its translation namespace under `src/app/<module>/utils/locales.ts`. The file is auto-loaded at boot — you don't import it from anywhere; the framework picks it up via the module loader.
|
|
43
61
|
|
|
@@ -74,31 +92,25 @@ t("products.outOfStock", { name: "Pen", count: 0 });
|
|
|
74
92
|
|
|
75
93
|
Placeholders use the `:name` syntax. The lookup helper substitutes them from the second argument.
|
|
76
94
|
|
|
77
|
-
## Looking up translations —
|
|
95
|
+
## Looking up translations — always `t()`
|
|
78
96
|
|
|
79
|
-
|
|
97
|
+
**Rule: app code translates with `t()` from `@warlock.js/core`.** It works everywhere: controllers, services, page and layout loaders, `metadata`, events and jobs. It returns a `string`.
|
|
80
98
|
|
|
81
99
|
```ts
|
|
82
|
-
// 1. Top-level helper — works inside or outside a request.
|
|
83
|
-
// Inside a request: uses request's locale.
|
|
84
|
-
// Outside: uses the global default locale.
|
|
85
100
|
import { t } from "@warlock.js/core";
|
|
86
101
|
|
|
87
102
|
t("products.notFound");
|
|
88
103
|
t("products.outOfStock", { name: "Pen", count: 0 });
|
|
89
|
-
|
|
90
|
-
// 2. On the request object — explicit, scoped to that request.
|
|
91
|
-
request.t("products.notFound");
|
|
92
|
-
|
|
93
|
-
// 3. Alias for request.t — same behavior.
|
|
94
|
-
request.trans("products.notFound");
|
|
95
104
|
```
|
|
96
105
|
|
|
97
|
-
|
|
106
|
+
- Inside a request (a controller or a page loader) it uses the request's locale. In a page it first checks the route's `locales.json` snapshot, then falls back to module translations (`groupedTranslations` or module `locales.json`).
|
|
107
|
+
- Outside a request (jobs, CLI) it uses the configured default locale.
|
|
108
|
+
- `request.t()` / `request.trans()` are the per-request binding that `t()` delegates to. Don't call them in app code: `t()` gives the same result without threading `request` through services.
|
|
109
|
+
- Don't wrap it in `String(...)`; it is already typed `string`.
|
|
98
110
|
|
|
99
111
|
### Web `useTrans()` key checking
|
|
100
112
|
|
|
101
|
-
When an app uses `@warlock.js/web`, `warlock dev` writes `.warlock/typings/translations.d.ts` from literal `groupedTranslations("group", { key: ... })` registrations. It augments web's `TranslationKeyRegistry`, so `useTrans()("products.notFound")` is checked against registered keys and a typo fails TypeScript. Before the generated file exists, `useTrans()` accepts `string` for a non-breaking first boot. Dynamic groups/keys and placeholders are not inferred.
|
|
113
|
+
When an app uses `@warlock.js/web`, `warlock dev` writes `.warlock/typings/translations.d.ts` from module `locales.json` dictionaries and literal `groupedTranslations("group", { key: ... })` registrations. It augments web's `TranslationKeyRegistry`, so `useTrans()("products.notFound")` is checked against registered keys and a typo fails TypeScript. Before the generated file exists, `useTrans()` accepts `string` for a non-breaking first boot. Dynamic TypeScript groups/keys and placeholders are not inferred.
|
|
102
114
|
|
|
103
115
|
`useTrans()` now works correctly across hydration (5.15.0): the hydration
|
|
104
116
|
payload ships a `translations` key with the active locale's keywords, and
|
|
@@ -144,7 +156,7 @@ export default {
|
|
|
144
156
|
request.setLocaleCode("ar");
|
|
145
157
|
```
|
|
146
158
|
|
|
147
|
-
After this, every `
|
|
159
|
+
After this, every `t(...)` and `getLocalized(...)` in the same request returns Arabic. Useful for user-preference overrides (e.g. "this user has set their language to ar in their profile — switch the request's locale on auth").
|
|
148
160
|
|
|
149
161
|
## Localized columns — `getLocalized`
|
|
150
162
|
|
|
@@ -241,6 +253,7 @@ The error's message is locale-aware — the `inject-request-context` middleware
|
|
|
241
253
|
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
242
254
|
import { type CreateProductSchema } from "../schema/create-product.schema";
|
|
243
255
|
import { createProductService } from "../services/create-product.service";
|
|
256
|
+
import { t } from "@warlock.js/core";
|
|
244
257
|
|
|
245
258
|
export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
|
|
246
259
|
request,
|
|
@@ -248,7 +261,7 @@ export const createProductController: GuardedRequestHandler<CreateProductSchema>
|
|
|
248
261
|
}) => {
|
|
249
262
|
const product = await createProductService(request.validated());
|
|
250
263
|
return response.success({
|
|
251
|
-
message:
|
|
264
|
+
message: t("products.created"),
|
|
252
265
|
product,
|
|
253
266
|
});
|
|
254
267
|
};
|
|
@@ -288,11 +301,11 @@ await Product.create({
|
|
|
288
301
|
GET /products/42?locale=ar
|
|
289
302
|
```
|
|
290
303
|
|
|
291
|
-
`request.getLocaleCode()` returns `"ar"` — every `
|
|
304
|
+
`request.getLocaleCode()` returns `"ar"` — every `t(...)` and resource-output `getLocalized` returns Arabic. No app code changes required.
|
|
292
305
|
|
|
293
306
|
## Gotchas
|
|
294
307
|
|
|
295
|
-
- **Locales files are auto-loaded — don't import them.** `src/app/<module>/utils/locales.
|
|
308
|
+
- **Locales files are auto-loaded — don't import them.** `src/app/<module>/utils/locales.json` is picked up by the module loader on boot. Importing legacy `locales.ts` manually causes double-registration warnings.
|
|
296
309
|
- **Group names should match module URL slugs.** `groupedTranslations("products", ...)` for the `/products` module. Mismatch breaks the convention but doesn't throw.
|
|
297
310
|
- **Every key needs every locale.** If a key is missing for the active locale, the lookup falls back to the key string itself (`"products.notFound"` literally appears in the response). Always ship all locales together when adding a key.
|
|
298
311
|
- **`t()` outside a request uses the global default.** `Application.environment === "test"` or background jobs without a request context get the configured `app.localeCode`, not whatever the most recent request used.
|
|
@@ -305,8 +318,8 @@ GET /products/42?locale=ar
|
|
|
305
318
|
|
|
306
319
|
- [`send-response/SKILL.md`](../send-response/SKILL.md) — error helpers that pair with translated messages (`response.notFound({ error: t("...") })`).
|
|
307
320
|
- [`define-resource/SKILL.md`](../define-resource/SKILL.md) — using `getLocalized` inside resource output for clean per-locale responses.
|
|
308
|
-
- [`create-module/SKILL.md`](../create-module/SKILL.md) — the `utils/locales.
|
|
309
|
-
- [`warlock-conventions/SKILL.md`](../warlock-conventions/SKILL.md) — `utils/locales.
|
|
321
|
+
- [`create-module/SKILL.md`](../create-module/SKILL.md) — the `utils/locales.json` file is part of the generated module scaffold.
|
|
322
|
+
- [`warlock-conventions/SKILL.md`](../warlock-conventions/SKILL.md) — `utils/locales.json` is auto-loaded; the suffix is mandatory.
|
|
310
323
|
|
|
311
324
|
## Route-owned web translations (5.17)
|
|
312
325
|
|
|
@@ -320,6 +333,6 @@ GET /products/42?locale=ar
|
|
|
320
333
|
}
|
|
321
334
|
```
|
|
322
335
|
|
|
323
|
-
This produces `account.title` and `account.actions.save`. Keep every leaf complete for the configured locale list. The page pipeline selects an immutable snapshot for its route source and locale. `useTrans()
|
|
336
|
+
This produces `account.title` and `account.actions.save`. Keep every leaf complete for the configured locale list. The page pipeline selects an immutable snapshot for its route source and locale. `useTrans()` and `t()` use that snapshot; a missing scoped key returns the key itself. With no route-locales manifest, the existing global `@mongez/localization` registry remains the legacy fallback path.
|
|
324
337
|
|
|
325
|
-
When a route-locales snapshot applies, `
|
|
338
|
+
When a route-locales snapshot applies, `t()` (through the request binding) and `request.transFrom(locale, key, placeholders?)` resolve through it; without one, they retain the existing global-registry behavior. The underlying `transFromKeywords(locale, keywords, key, placeholders?, converter?)` is supplied by `@mongez/localization` 3.5.0. Error pages rebind to their own snapshot, while framework fallback uses the app/root snapshot. Development HMR add/edit/delete publishes a new snapshot, and the snapshot revision keeps page-cache entries from reusing old JSON. Generated `.warlock` typings contain the flattened route keys; do not maintain a second handwritten key union.
|