@craft-ts/mcp 0.7.0-beta.17 → 0.7.0-beta.19
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/package.json
CHANGED
package/skills/craft-ts/SKILL.md
CHANGED
|
@@ -60,9 +60,18 @@ If MCP is not configured, read https://ng-angular-stack.github.io/craft/llms.txt
|
|
|
60
60
|
typecheck and emit nothing. Load `craft-ts-style` before touching one.
|
|
61
61
|
- Translations live in a `@craft-ts/i18n` catalogue: `defineCatalog` + `msg` for
|
|
62
62
|
the reference locale, `defineLocaleLike` for every other one, so a missing key
|
|
63
|
-
is a compile error.
|
|
64
|
-
|
|
63
|
+
is a compile error. A token may validate or parse its parameter with a
|
|
64
|
+
Standard Schema, and resolve its formatter from a Craft service; a project
|
|
65
|
+
generated with i18n also forbids visible literals in templates. No Effect
|
|
66
|
+
import — use `@craft-ts/i18n-effect` only inside an Effect program. Load
|
|
65
67
|
`craft-ts-i18n` before adding a key, a locale or a token.
|
|
68
|
+
- Forms start with `state` + `insertForm`. Choose `insertSelectFormTree` for a
|
|
69
|
+
nested field, `insertFormAttributes` for validators/visibility,
|
|
70
|
+
`insertFormSchema` for whole-form validation and `insertFormSubmit` for a
|
|
71
|
+
mutation. Bind the selected field with `CraftFieldDirective`; expose
|
|
72
|
+
`field.exceptions` through `fieldErrorNode`, and read submission state with
|
|
73
|
+
`form().submitting()` / `form().hasSubmitExceptions()`. Do not reach for
|
|
74
|
+
native `FormData` as the primary form model.
|
|
66
75
|
- Run existing architecture tests. Do not add an architecture rule for the feature.
|
|
67
76
|
- Keep `npm run typecheck` in the project CI; for generated projects this is
|
|
68
77
|
already wired into `.github/workflows/ci.yml` alongside the architecture
|
|
@@ -22,7 +22,8 @@ suite that already exists.
|
|
|
22
22
|
|
|
23
23
|
## 1. Bootstrap a new project
|
|
24
24
|
|
|
25
|
-
Prefer `craft create`, which asks for
|
|
25
|
+
Prefer `craft create`, which asks for the application type first, recommends
|
|
26
|
+
EffectTS v4 for a full-stack backend, and then asks for agent integrations. It
|
|
26
27
|
generates the application, API/page example, routes, ESLint, unit tests,
|
|
27
28
|
architecture suite and Playwright commands together:
|
|
28
29
|
|
|
@@ -5,9 +5,11 @@ description: Build and review type-safe internationalisation in a CraftTS projec
|
|
|
5
5
|
|
|
6
6
|
# CraftTS type-safe i18n
|
|
7
7
|
|
|
8
|
-
`@craft-ts/i18n`
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
`@craft-ts/i18n` integrates with CraftTS for DI-aware translation tokens. The
|
|
9
|
+
catalogue remains declarative, and non-DI messages still work with `runtime.t`.
|
|
10
|
+
Use the CraftTS-bound translator whenever a token yields a service. Do not reach
|
|
11
|
+
for Effect to translate a string; use `@craft-ts/i18n-effect` only inside an
|
|
12
|
+
Effect program.
|
|
11
13
|
|
|
12
14
|
The contract it enforces, all at typecheck time:
|
|
13
15
|
|
|
@@ -89,6 +91,93 @@ return { language, setLocale: language.setLocale, translate: runtime.bind(langua
|
|
|
89
91
|
`bind(...)('key', params)` returns a generator the template yields, like any
|
|
90
92
|
other Craft reader.
|
|
91
93
|
|
|
94
|
+
### DI-aware tokens
|
|
95
|
+
|
|
96
|
+
Any token factory accepts a **generator function** in place of the adapter. The
|
|
97
|
+
yielded services are carried into the translation reader's component dependency
|
|
98
|
+
contract:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const amount = money('amount', function* () {
|
|
102
|
+
const currency = yield* ClientCurrency();
|
|
103
|
+
return { currency: currency.code, minimumFractionDigits: 2 };
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const catalog = defineCatalog({
|
|
107
|
+
order: msg`Order total ${amount}.`,
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Render it with the translator returned by `runtime.bind(...)`, and **pass the
|
|
112
|
+
reader** — as a child or as an attribute value:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
p(translate('order', { amount: 1234.5 }));
|
|
116
|
+
p({ title: translate('order', { amount: 1234.5 }) }, 'Order');
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Both carry the dependency, so the component and route DI checks fail
|
|
120
|
+
compilation if `ClientCurrency` is not provided. Do not drive the reader
|
|
121
|
+
yourself (`yield* translate(...)()`): it renders the same string but the
|
|
122
|
+
dependency disappears from the check.
|
|
123
|
+
|
|
124
|
+
`runtime.t` accepts `StaticTranslationKey` only — the keys that resolve no
|
|
125
|
+
service — so a DI message on that path is a compile error, not a runtime one.
|
|
126
|
+
An arrow function that returns a generator is refused: the options factory must
|
|
127
|
+
be a `function*`.
|
|
128
|
+
|
|
129
|
+
### No visible literal in a template
|
|
130
|
+
|
|
131
|
+
A project generated with i18n ships `craft-ts/require-i18n-text` in its ESLint
|
|
132
|
+
configuration: a static string in `heading`/`p`/`label`/`button`/`a`/`option`/…
|
|
133
|
+
or in a `placeholder`, `aria-label` or `title` attribute is an error. Put the
|
|
134
|
+
copy in `src/i18n/catalog.ts` (and in every locale) and read it with
|
|
135
|
+
`i18n.t(...)`.
|
|
136
|
+
|
|
137
|
+
Wrapping the literal does not hide it — concatenation, template text, ternary
|
|
138
|
+
branches, `||` fallbacks and children arrays are all inspected:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
p('Total: ' + i18n.t('cart.total', { amount })); // reported: 'Total: '
|
|
142
|
+
p(i18n.t('cart.totalLine', { amount })); // the whole sentence is a key
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Only text carrying letters counts, so `first + ' ' + last` is fine. The key and
|
|
146
|
+
parameters of `i18n.t(...)`, generator children, catalogue files, server files
|
|
147
|
+
and tests are all exempt. A project generated without i18n does not get the
|
|
148
|
+
rule.
|
|
149
|
+
|
|
150
|
+
A project token does the same through `defineToken`, and declares no `format`
|
|
151
|
+
when the formatter only exists at render time:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const weight = defineToken({
|
|
155
|
+
name: 'weight',
|
|
156
|
+
kind: 'weight',
|
|
157
|
+
resolveFormatter: function* () {
|
|
158
|
+
const units = yield* Units();
|
|
159
|
+
const unit = (yield* units.system()) === 'imperial' ? 'pound' : 'kilogram';
|
|
160
|
+
return (value: number, context) =>
|
|
161
|
+
new Intl.NumberFormat(context.locale, { style: 'unit', unit }).format(value);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Schema-declared parameters
|
|
167
|
+
|
|
168
|
+
The adapter position also takes a Standard Schema (Zod, Valibot, ArkType — the
|
|
169
|
+
same contract as `state`, `query` and forms). The parameter type becomes the
|
|
170
|
+
schema's input and the formatter receives its output, so the schema parses
|
|
171
|
+
once, in the catalogue:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const placedAt = dateLong('placedAt', z.coerce.date());
|
|
175
|
+
translate('order', { placedAt: '2026-08-25T14:30:00Z' });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`defineToken` takes the same `schema` field and may combine it with
|
|
179
|
+
`resolveFormatter`.
|
|
180
|
+
|
|
92
181
|
## With Effect
|
|
93
182
|
|
|
94
183
|
`@craft-ts/i18n-effect` is the adapter, and only the adapter:
|