@spintax/core 0.3.3 → 0.4.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 +42 -0
- package/README.md +32 -10
- package/dist/index.cjs +8 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,48 @@
|
|
|
3
3
|
All notable changes to `@spintax/core` are documented here. This project adheres to
|
|
4
4
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## 0.4.0 — 2026-08-17
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`plural.locale-missing` — a warning where `validate` used to be silent** (issue #65, reported
|
|
11
|
+
from a pipeline rendering ~1000 articles per campaign).
|
|
12
|
+
|
|
13
|
+
`validate` files no arity *verdict* without a locale, and that stays: the template may well be
|
|
14
|
+
correct for the locale the host will render with, so failing it here would fail a good template
|
|
15
|
+
for a fact the caller never claimed. But `render` has no such luxury — it resolves against the
|
|
16
|
+
2-form default — so a `{plural …}` block with any other form count reached finished text as the
|
|
17
|
+
fullwidth-brace fallback `{plural …}`, with nothing said upfront. Downstream checks that scan
|
|
18
|
+
for `{`/`}` do not see those braces.
|
|
19
|
+
|
|
20
|
+
The warning fires only where the risk is: **no locale normalized, and the form count is not the
|
|
21
|
+
render default**. A 2-form block stays silent, because the default resolves it. `valid` is
|
|
22
|
+
unchanged in every case (`valid ⇔ no error-severity diagnostic`), and supplying any locale
|
|
23
|
+
replaces the warning with the real verdict — nothing for `ru`, `plural.arity` for `en`.
|
|
24
|
+
|
|
25
|
+
Carries `data: { got, defaultArity }`. The default arity is derived from the same table `render`
|
|
26
|
+
uses rather than written as a literal, so the two cannot come to disagree.
|
|
27
|
+
|
|
28
|
+
Mirrored into `spintax/core`, the WordPress plugin and `spintax-core` (Python) in the same
|
|
29
|
+
change, and pinned by a corpus fixture. Note the gate's shape: the shared corpus asserts
|
|
30
|
+
diagnostics as a SUBSET, so it can pin that the warning IS emitted but not that it is absent on
|
|
31
|
+
a 2-form block — that negative control lives in each engine's own suite.
|
|
32
|
+
|
|
33
|
+
## 0.3.4 — 2026-08-08
|
|
34
|
+
|
|
35
|
+
Docs-only release — no engine changes; every byte of `dist/` behavior is 0.3.3's.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **README catches up with the ecosystem.** The parity paragraph now states the real contract —
|
|
40
|
+
one syntax surface across five independent engines (npm / Packagist / PyPI / Object Pascal /
|
|
41
|
+
the WordPress plugin) held by the shared 234-fixture corpus — instead of the original
|
|
42
|
+
two-engine wording. New "Using it without writing code" section and refreshed links:
|
|
43
|
+
the [`n8n-nodes-spintax`](https://www.npmjs.com/package/n8n-nodes-spintax) community node
|
|
44
|
+
(released 2026-08-07), the spintax.net playground, the Telegram bot, and Spintax Studio.
|
|
45
|
+
Internal spec-section references dropped from public copy.
|
|
46
|
+
- **`package.json`**: description mentions the n8n node; `n8n` added to keywords.
|
|
47
|
+
|
|
6
48
|
## 0.3.3 — 2026-08-07
|
|
7
49
|
|
|
8
50
|
`validate()` scales. No output changes anywhere: a 464-document differential over every
|
package/README.md
CHANGED
|
@@ -14,16 +14,20 @@ parse, render, validate, extract, analyze, and neutralize spintax templates.
|
|
|
14
14
|
|
|
15
15
|
- **Zero runtime dependencies.** Runs unchanged on Cloudflare Workers, Node 18+, and in the browser.
|
|
16
16
|
- **ESM-first, dual CJS.** Ships `.d.ts` types for both.
|
|
17
|
-
- **
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
- **One syntax contract, [five independent engines](https://spintax.net/spintax-engines/).**
|
|
18
|
+
This package, [`spintax/core`](https://packagist.org/packages/spintax/core) (PHP),
|
|
19
|
+
[`spintax-core`](https://pypi.org/project/spintax-core/) (Python), an Object Pascal engine, and
|
|
20
|
+
the [Spintax WordPress plugin](https://wordpress.org/plugins/spintax/) are all held to the same
|
|
21
|
+
behavior contract by a **shared golden corpus of 234 fixtures** — deterministic verdicts, plural
|
|
22
|
+
buckets, conditionals, `#set`/`#def` semantics and post-processing agree everywhere, so a
|
|
23
|
+
template is an asset you can move between runtimes, not a lock-in.
|
|
22
24
|
- **MIT** licensed.
|
|
23
25
|
|
|
24
26
|
> **Status: released & stable.** Feature-complete — parse / render / validate / extract / analyze /
|
|
25
|
-
> neutralize
|
|
26
|
-
>
|
|
27
|
+
> neutralize — with the public API proven by real consumers: a reference Cloudflare Worker, the
|
|
28
|
+
> [@spintaxnetbot](https://t.me/spintaxnetbot) Telegram bot, the
|
|
29
|
+
> [spintax.net playground](https://spintax.net/play/), and the
|
|
30
|
+
> [n8n community node](https://www.npmjs.com/package/n8n-nodes-spintax).
|
|
27
31
|
|
|
28
32
|
## Install
|
|
29
33
|
|
|
@@ -149,6 +153,19 @@ render('%bio%', { context: { bio: neutralize('Save {50|60}% today') }, postProce
|
|
|
149
153
|
// → "Save {50|60}% today" (the braces stay literal, not a random pick)
|
|
150
154
|
```
|
|
151
155
|
|
|
156
|
+
## Using it without writing code
|
|
157
|
+
|
|
158
|
+
- **n8n** — install [`n8n-nodes-spintax`](https://www.npmjs.com/package/n8n-nodes-spintax)
|
|
159
|
+
(Settings → Community Nodes). One node, five operations: Render, Render Many, a two-output
|
|
160
|
+
Validate, and the LLM authoring/repair prompt builders — a Sheets-to-outbox personalization
|
|
161
|
+
flow or a full AI authoring loop with no code and no credentials on the node's side.
|
|
162
|
+
- **Telegram** — [@spintaxnetbot](https://t.me/spintaxnetbot): validate, preview, and AI-draft
|
|
163
|
+
templates in chat.
|
|
164
|
+
- **Browser** — the [spintax.net playground](https://spintax.net/play/): live validation and
|
|
165
|
+
N-variant rendering, entirely client-side (it runs this very package).
|
|
166
|
+
- **Windows** — [Spintax Studio](https://apps.microsoft.com/detail/9mw3ch7b530p), a native
|
|
167
|
+
editor in the Microsoft Store (renders via the family's Object Pascal engine; same corpus).
|
|
168
|
+
|
|
152
169
|
## Determinism & RNG
|
|
153
170
|
|
|
154
171
|
With a `seed`, `render` is reproducible within this engine. Cross-engine RNG-sequence parity
|
|
@@ -157,10 +174,15 @@ plural buckets, conditional truthiness, `#set`/`#def` semantics, post-process ou
|
|
|
157
174
|
|
|
158
175
|
## Links
|
|
159
176
|
|
|
160
|
-
- 📦 npm — [`@spintax/core`](https://www.npmjs.com/package/@spintax/core)
|
|
161
|
-
-
|
|
177
|
+
- 📦 npm — [`@spintax/core`](https://www.npmjs.com/package/@spintax/core) ·
|
|
178
|
+
[`n8n-nodes-spintax`](https://www.npmjs.com/package/n8n-nodes-spintax) (the n8n node)
|
|
179
|
+
- 🧪 Try it — [playground](https://spintax.net/play/) ·
|
|
180
|
+
[@spintaxnetbot](https://t.me/spintaxnetbot) (Telegram: validate + preview + AI `/draft`)
|
|
181
|
+
- 🖥️ Desktop — [Spintax Studio](https://apps.microsoft.com/detail/9mw3ch7b530p) (Microsoft Store)
|
|
182
|
+
- 🧬 The family — [all five engines, one corpus](https://spintax.net/spintax-engines/)
|
|
183
|
+
(PHP · Python · Pascal · WordPress)
|
|
162
184
|
- 🧩 Source — [investblog/spintax-js](https://github.com/investblog/spintax-js)
|
|
163
|
-
- 🌐
|
|
185
|
+
- 🌐 Docs & guides — [spintax.net](https://spintax.net)
|
|
164
186
|
- 🏠 Maintainer — [301.st](https://301.st)
|
|
165
187
|
|
|
166
188
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -399,6 +399,7 @@ function pluralArity(baseLang) {
|
|
|
399
399
|
return 2;
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
|
+
var DEFAULT_PLURAL_ARITY = pluralArity("");
|
|
402
403
|
function pluralFor(baseLang, n, forms) {
|
|
403
404
|
const abs = Math.abs(n);
|
|
404
405
|
const mod10 = abs % 10;
|
|
@@ -581,8 +582,8 @@ function checkPlurals(text, idx, locale, out) {
|
|
|
581
582
|
out.push(err("plural.nested-brackets", "{plural ...}: forms must not contain nested spintax brackets. Extract via #def first \u2014 a #set is substituted verbatim and would put the brackets straight back.", at));
|
|
582
583
|
continue;
|
|
583
584
|
}
|
|
585
|
+
const count = block.formsRaw.split("|").length;
|
|
584
586
|
if (arity > 0) {
|
|
585
|
-
const count = block.formsRaw.split("|").length;
|
|
586
587
|
if (count !== arity) {
|
|
587
588
|
out.push(err(
|
|
588
589
|
"plural.arity",
|
|
@@ -590,6 +591,12 @@ function checkPlurals(text, idx, locale, out) {
|
|
|
590
591
|
{ ...at, data: { expected: arity, got: count } }
|
|
591
592
|
));
|
|
592
593
|
}
|
|
594
|
+
} else if (count !== DEFAULT_PLURAL_ARITY) {
|
|
595
|
+
out.push(warn(
|
|
596
|
+
"plural.locale-missing",
|
|
597
|
+
`{plural ...}: ${count} forms, but no locale was supplied. render defaults to ${DEFAULT_PLURAL_ARITY} forms and leaves this block unresolved \u2014 pass the locale you will render with.`,
|
|
598
|
+
{ ...at, data: { got: count, defaultArity: DEFAULT_PLURAL_ARITY } }
|
|
599
|
+
));
|
|
593
600
|
}
|
|
594
601
|
}
|
|
595
602
|
}
|