cronli5 0.3.4 → 0.7.2
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 +97 -0
- package/README.md +39 -6
- package/cronli5.min.js +2 -2
- package/dist/cronli5.cjs +13 -6
- package/dist/cronli5.js +13 -6
- package/dist/lang/de.cjs +13 -6
- package/dist/lang/de.js +13 -6
- package/dist/lang/en.cjs +13 -6
- package/dist/lang/en.js +13 -6
- package/dist/lang/es.cjs +13 -6
- package/dist/lang/es.js +13 -6
- package/dist/lang/fi.cjs +13 -6
- package/dist/lang/fi.js +13 -6
- package/dist/lang/fr.cjs +1210 -0
- package/dist/lang/fr.js +1186 -0
- package/dist/lang/pt.cjs +1591 -0
- package/dist/lang/pt.js +1567 -0
- package/dist/lang/zh.cjs +57 -9
- package/dist/lang/zh.js +57 -9
- package/package.json +13 -2
- package/src/core/cadence.ts +25 -12
- package/src/lang/de/index.ts +2 -2
- package/src/lang/en/index.ts +2 -2
- package/src/lang/es/index.ts +2 -2
- package/src/lang/fi/index.ts +2 -2
- package/src/lang/fr/dialects.ts +49 -0
- package/src/lang/fr/index.ts +2115 -0
- package/src/lang/fr/notes.md +280 -0
- package/src/lang/fr/status.json +8 -0
- package/src/lang/pt/dialects.ts +56 -0
- package/src/lang/pt/index.ts +2803 -0
- package/src/lang/pt/notes.md +199 -0
- package/src/lang/pt/status.json +8 -0
- package/src/lang/zh/index.ts +60 -5
- package/src/lang/zh/notes.md +16 -4
- package/src/lang/zh/status.json +10 -1
- package/types/core/cadence.d.ts +1 -0
- package/types/lang/fr/dialects.d.ts +11 -0
- package/types/lang/fr/index.d.ts +4 -0
- package/types/lang/pt/dialects.d.ts +13 -0
- package/types/lang/pt/index.d.ts +4 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Português (pt, target pt-BR) — Language Notes
|
|
2
|
+
|
|
3
|
+
**donor: es.** Derived by sibling-derivation (tooling/docs/language-pipeline.md):
|
|
4
|
+
the Spanish module supplies the structure, plan override, OR-frame, predicates,
|
|
5
|
+
re-strategies, and dialect mechanism; this doc records only where **pt-BR
|
|
6
|
+
diverges** from that donor. The shipped table today is **pt-BR**; pt-PT is a
|
|
7
|
+
future dialect axis (below). The corpus translation (Stage 2) and renderer port
|
|
8
|
+
(Stage 4) both follow this contract. The donor's own contract is
|
|
9
|
+
[`../es/notes.md`](../es/notes.md).
|
|
10
|
+
|
|
11
|
+
## Anchors
|
|
12
|
+
|
|
13
|
+
Brazilian norm (VOLP / Academia Brasileira de Letras, plus cronstrue `pt_BR`):
|
|
14
|
+
lowercase month and weekday names, **24-hour zero-padded clock by default**
|
|
15
|
+
("às 09:30", "às 17:00"; `{ampm: true}` opts into the 12-hour clock), day
|
|
16
|
+
periods on the 12-hour clock (madrugada 1–5, manhã 6–11, tarde 12–18, noite
|
|
17
|
+
19–24 — see the note below; this is the one boundary that may differ from es),
|
|
18
|
+
"meio-dia" / "meia-noite" for exact 12:00 / 0:00 (12-hour clock), colon time
|
|
19
|
+
separator.
|
|
20
|
+
|
|
21
|
+
**Clock — decided: "às 09:00" (24h zero-padded), not the colloquial "às 9h".**
|
|
22
|
+
Rationale: parallels the es donor and cronstrue `pt_BR`'s reference rendering,
|
|
23
|
+
and keeps the corpus comparable field-by-field with es; the "9h"/"9h30" form is
|
|
24
|
+
genuinely common in pt-BR casual writing but is a *separate register*, deferred
|
|
25
|
+
to a future custom/dialect style exactly as es kept `hSuffix` opt-in.
|
|
26
|
+
|
|
27
|
+
**Article agreement (the a+a contraction):** the preposition *a* + the feminine
|
|
28
|
+
article contracts — *a + a hora 1* → **à 1h / à 01:00** (grave accent, singular),
|
|
29
|
+
*a + as horas* → **às** otherwise. This mirrors es's singular-article hold for
|
|
30
|
+
one o'clock ("a la 1") but in pt the contraction is *written with the accent*:
|
|
31
|
+
"à 01:00" at hour 1, "às 09:00" at every other hour, on **both** clocks. Hours
|
|
32
|
+
zero-pad to two digits on the 24-hour clock ("às 09:00"); the 12-hour clock
|
|
33
|
+
leaves the hour unpadded ("às 9 da manhã").
|
|
34
|
+
|
|
35
|
+
## Day periods (12-hour)
|
|
36
|
+
|
|
37
|
+
"da madrugada / da manhã / da tarde / da noite" (contraction *de + a* = *da*).
|
|
38
|
+
**Decided (panel-confirmed): madrugada 1–5, manhã 6–11, tarde 12–18, noite
|
|
39
|
+
19–24.** es uses tarde 12–19 / noche 20–24, but pt-BR puts *noite* earlier;
|
|
40
|
+
the blind pt-BR panel unanimously affirmed the 19h boundary (broadcast/weather
|
|
41
|
+
register and the "jornal da noite" cultural anchor place noite firmly at 19h —
|
|
42
|
+
tighter than the loose popular sense that some extend to 18h, and the better
|
|
43
|
+
choice for an unambiguous description). 18h reads *da tarde*, 19h+ *da noite*.
|
|
44
|
+
"meio-dia" / "meia-noite" for exact 12:00 / 0:00.
|
|
45
|
+
|
|
46
|
+
## Weekday recurrence
|
|
47
|
+
|
|
48
|
+
es uses the plural article "los lunes" = every Monday. **Decided (panel-
|
|
49
|
+
confirmed): keep *-feira* throughout** — the full forms are the standard pt-BR
|
|
50
|
+
written/spoken register and dropping *-feira* is too informal for an unambiguous
|
|
51
|
+
description. The *-feira* element attaches to weekdays Mon–Fri (segunda-feira …
|
|
52
|
+
sexta-feira); sábado and domingo have none. The resolved sub-rules:
|
|
53
|
+
|
|
54
|
+
- **Single weekday recurrence + a clock time → "toda segunda-feira às 9 da
|
|
55
|
+
manhã".** The plural-article recurrence "às segundas-feiras" before an "às …"
|
|
56
|
+
time clashed aurally (the double-"às"); the panel's fix is the singular
|
|
57
|
+
"toda X" head, which reads naturally and keeps the meaning. This applies
|
|
58
|
+
wherever a single weekday leads a clause that a clock time follows (incl.
|
|
59
|
+
"toda segunda-feira de junho às 9 da manhã").
|
|
60
|
+
- **Standalone single weekday recurrence (no following time) keeps the plural
|
|
61
|
+
article** "às segundas-feiras" (e.g. "a cada 15 minutos às segundas-feiras",
|
|
62
|
+
trailing-qualifier "… às segundas-feiras").
|
|
63
|
+
- **Lists carry the *-feira* suffix on the last *-feira* day only**, the
|
|
64
|
+
idiomatic pt-BR suffix-ellipsis: "às segundas, quartas e sextas-feiras";
|
|
65
|
+
"às terças, quintas-feiras, sábados e domingos" (terça bare, quinta is the
|
|
66
|
+
last *-feira* day so it carries the suffix, sábado/domingo never do). All
|
|
67
|
+
panels affirmed this is correct and unambiguous, not an inconsistency.
|
|
68
|
+
- **Ranges carry *-feira* on the last term only**: "de segunda a sexta-feira"
|
|
69
|
+
(the asymmetric form is the idiomatic pt-BR range shorthand — not
|
|
70
|
+
"de segunda-feira a sexta-feira").
|
|
71
|
+
- **Single weekday in an OR-union arm reads the Brazilian recurrence**
|
|
72
|
+
"às [weekday]s-feiras" / "aos domingos" (NOT "em qualquer [weekday]", which
|
|
73
|
+
reads slightly Iberian); a **range** arm keeps the nominal head
|
|
74
|
+
"em qualquer dia de segunda a sexta-feira" (a range needs the head "dia").
|
|
75
|
+
- **Quartz nth-weekday ordinal collision:** when the ordinal word would collide
|
|
76
|
+
with the weekday name ("segunda segunda-feira" for `1#2`), use the ordinal
|
|
77
|
+
digit "na 2ª segunda-feira do mês". Non-colliding ordinals keep the word form
|
|
78
|
+
("na última sexta-feira", "a primeira segunda-feira").
|
|
79
|
+
|
|
80
|
+
## Ordinals / dates
|
|
81
|
+
|
|
82
|
+
es: "el 1 de junio" / "el día N" / "el N de cada mes". **Decided for pt-BR:
|
|
83
|
+
"(no) dia 1 de junho"** — pt-BR routinely uses the cardinal with the noun *dia*
|
|
84
|
+
("dia 1", "dia 13"), so the donor's "el día N" maps cleanly to "dia N". Ranges:
|
|
85
|
+
"do dia 1 ao dia 15 do mês" (contractions *de+o=do*, *a+o=ao*). The es bare
|
|
86
|
+
"el 1 de junio" → "**dia 1 de junho**".
|
|
87
|
+
|
|
88
|
+
- **Decided (panel-confirmed): the 1st of the month is the ordinal "dia 1º"**;
|
|
89
|
+
every other day stays cardinal. The ordinal first is a deep pt-BR norm
|
|
90
|
+
(calendars, official/legal texts, speech); cardinal "dia 1" reads as a typo or
|
|
91
|
+
informal shorthand. The "1º" carries into the date-range and OR-union arms
|
|
92
|
+
too: "do dia 1º ao dia 15", "seja no dia 1º …". (Ranges carry the ordinal on
|
|
93
|
+
the first term and cardinal on the rest, the normal pt-BR pattern.) The
|
|
94
|
+
W-operator proximity preposition is the dative "próximo **ao** dia 15" (not
|
|
95
|
+
"próximo do dia 15") — proximity-to-a-target takes *a+o=ao*.
|
|
96
|
+
- Quartz nth-weekday ordinals: primeiro/primeira, segundo/segunda, terceiro,
|
|
97
|
+
quarto, quinto — **gendered** (see below).
|
|
98
|
+
|
|
99
|
+
## Contractions (the big es→pt divergence — renderer logic, not string swaps)
|
|
100
|
+
|
|
101
|
+
Portuguese fuses prepositions with the following article; the renderer must
|
|
102
|
+
form these wherever es emitted a bare preposition + article:
|
|
103
|
+
|
|
104
|
+
- *de* + o/a/os/as → **do / da / dos / das** ("do mês", "da manhã", "das 9").
|
|
105
|
+
- *em* + o/a/os/as → **no / na / nos / nas** ("no dia 1", "no minuto 30",
|
|
106
|
+
"na hora").
|
|
107
|
+
- *a* + a/as → **à / às** (clock and weekday recurrence; grave accent).
|
|
108
|
+
*a* + o/os → **ao / aos** (date ranges "ao dia 15").
|
|
109
|
+
- *por* generally stays separate in these phrasings (not needed as a fused
|
|
110
|
+
form for the cron domain; noted for completeness).
|
|
111
|
+
|
|
112
|
+
This contraction layer is the principal structural divergence from es and is
|
|
113
|
+
where most RED in the TDD port is expected — it is **gender/number-driven
|
|
114
|
+
formation**, not a lexical substitution.
|
|
115
|
+
|
|
116
|
+
## Connectives
|
|
117
|
+
|
|
118
|
+
- and → **e** (RAE-style coma ante "y" has **no pt-BR analog** — pt does *not*
|
|
119
|
+
put a comma before *e* in a simple series; **FLAGGED**: the donor's "coma
|
|
120
|
+
ante 'y'" re-strategy in the day-period join must be *dropped*, not ported.
|
|
121
|
+
This is a real renderer divergence, not a string swap.)
|
|
122
|
+
- or → **ou** (the OR-union connector; see re-strategies).
|
|
123
|
+
- range / until → **a** ("de … a …") and **até** where a terminal "until"
|
|
124
|
+
reads better; default to **a** to mirror es "de … a …".
|
|
125
|
+
|
|
126
|
+
## Names, gender, agreement
|
|
127
|
+
|
|
128
|
+
- Lowercase months and weekdays (confirmed pt-BR norm, VOLP).
|
|
129
|
+
- **Gender/agreement the renderer must handle (es→pt divergence):**
|
|
130
|
+
- Weekdays are **feminine** in pt (a segunda-feira) — the recurrence article
|
|
131
|
+
is *as* → *às*; es's masculine "los lunes" does not carry over. This drives
|
|
132
|
+
"às segundas-feiras", "qualquer segunda-feira".
|
|
133
|
+
- Quartz nth ordinals agree with the (feminine) weekday: "a primeira
|
|
134
|
+
segunda-feira", "o último domingo" (domingo masculine), "a última
|
|
135
|
+
sexta-feira". The renderer must select ordinal gender by weekday gender —
|
|
136
|
+
es used invariant "primer/último". **FLAGGED** as needing real agreement
|
|
137
|
+
logic.
|
|
138
|
+
- "todo(s)" / "cada" agreement: "todos os dias" (m.pl.), "cada mês" (m.),
|
|
139
|
+
"cada hora" (f.) — gendered determiners where es had "todos los días" /
|
|
140
|
+
"cada".
|
|
141
|
+
|
|
142
|
+
## Ported re-strategies (language-neutral; pt forms)
|
|
143
|
+
|
|
144
|
+
- **Per-hour windows for wildcard minutes over hour lists** (es §"wildcard
|
|
145
|
+
minutes over hour lists render as per-hour windows"): keep the strategy; pt
|
|
146
|
+
form "das 9 às 9:59 da manhã" (note *das* = de+as, *às* = a+as).
|
|
147
|
+
- **OR-union unified frame:** es "ya sea X o Y" → **"seja X ou Y"
|
|
148
|
+
(panel-confirmed).** All three personas read it as an unambiguous inclusive
|
|
149
|
+
OR; the "seja" frame is cleaner than a bare "X ou Y" and there is no
|
|
150
|
+
intersection misreading. "ou seja" is avoided — it means "that is/i.e." The
|
|
151
|
+
shared month is fronted once and the arms are month-less, exactly as in es.
|
|
152
|
+
The weekday arm wording is resolved under *Weekday recurrence* above (single
|
|
153
|
+
weekday → "às [weekday]s-feiras"; range → "em qualquer dia de segunda a
|
|
154
|
+
sexta-feira").
|
|
155
|
+
- **No-fold month range:** a month range never folds into another phrase
|
|
156
|
+
("dia 1 de junho a setembro" parses as "(dia 1 de junho) a setembro"); dates
|
|
157
|
+
scope it instead ("dia 1 de cada mês, de junho a setembro"); mixed lists
|
|
158
|
+
repeat the preposition per piece ("em janeiro e de março a junho"). Same rule
|
|
159
|
+
as es and English.
|
|
160
|
+
- **Step-flattening:** step segments inside lists always flatten into their
|
|
161
|
+
fires — months, weekdays, dates, minutes, seconds — no raw step token reaches
|
|
162
|
+
the output. Identical to es.
|
|
163
|
+
- **Anchored minutes/seconds** read as "no minuto 30 de cada hora" (em+o=no),
|
|
164
|
+
the donor's "en el minuto 30 de cada hora" — not a calque of "past the hour".
|
|
165
|
+
|
|
166
|
+
## Dialect axis (future)
|
|
167
|
+
|
|
168
|
+
pt-PT is a **future dialect** (clock/lexical divergences from pt-BR, e.g. some
|
|
169
|
+
date/register differences), mirroring es's es-ES / es-419 split. **One `pt`
|
|
170
|
+
table today = pt-BR.** A future `pt-PT` (and any regional pt-BR style such as a
|
|
171
|
+
"9h" colloquial-clock custom field) would clear its own native panel before
|
|
172
|
+
shipping, per the dialect rules in the pipeline.
|
|
173
|
+
|
|
174
|
+
## Residuals inherited from es (NOT fixed here — es+pt follow-up)
|
|
175
|
+
|
|
176
|
+
The blind pt-BR panel's technical reviewer flagged two issues that are **shared
|
|
177
|
+
artifacts of the es donor corpus**, not pt regressions, so they were left in the
|
|
178
|
+
pt corpus to keep it field-comparable with es and are tracked as a joint es+pt
|
|
179
|
+
follow-up (docs/backlog.md, per-language follow-ups):
|
|
180
|
+
|
|
181
|
+
- **Hour-window overlap in `* 2/4,18-20 * * *`.** Hour 18 is named twice — once
|
|
182
|
+
as the 2/4 step arm's per-hour window ("das 6 às 6:59 da tarde") and again as
|
|
183
|
+
the left endpoint of the 18-20 range window ("das 6 da tarde às 8:59 da
|
|
184
|
+
noite"). The fire set is correct (no value dropped or understated); the
|
|
185
|
+
overlap is a rendering-clarity artifact present identically in es.
|
|
186
|
+
- **OR DOW-arm "e" bracketing** in `… ou de segunda a sexta-feira e aos
|
|
187
|
+
domingos` (`0 0 1 * 0,1-5`, `0 0 1 6-9 0,1-5`). The internal "e" joining
|
|
188
|
+
Mon–Fri + Sun inside the second OR arm could be misparsed as a top-level
|
|
189
|
+
conjunction. The meaning is correct and the construction is the same one the
|
|
190
|
+
es donor uses ("o de lunes a viernes y los domingos"); fixing it is an es+pt
|
|
191
|
+
bracketing change, not a pt-only one.
|
|
192
|
+
|
|
193
|
+
## Known trade-offs
|
|
194
|
+
|
|
195
|
+
- `short` only switches spelled numbers to digits; pt name abbreviations
|
|
196
|
+
(seg., qua.) are not yet implemented (same residue as es).
|
|
197
|
+
- The grave-accent contraction (à/às) is correctness-critical for the 1-o'clock
|
|
198
|
+
and weekday-recurrence forms; the renderer forms it programmatically rather
|
|
199
|
+
than hard-coding strings.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Portuguese",
|
|
3
|
+
"status": "beta",
|
|
4
|
+
"humanReview": null,
|
|
5
|
+
"modelReview": "sibling-derived from es (the validated Romance sibling): the es renderer's structure was ported (plan override, OR-union frame, parity predicates, re-strategies, dialect scaffold) and its lexicon translated to pt-BR, then TDD'd to green against the reviewed pt-BR corpus (277 entries). The corpus was a candidate translated from the reviewed es corpus and finalized by a blind 3-persona pt-BR Sonnet panel (everyday / copy-editor / technical) before the port (corpus -> review -> port; tooling/docs/language-pipeline.md). The es->pt divergences the panel and TDD surfaced and the renderer now handles: preposition+article contraction (do/da/no/na/à/às/ao/aos, formed gender/number-driven, not as strings), gender agreement (feminine -feira weekdays vs masculine domingo/sábado, gendered Quartz ordinals and the 'duas horas' feminine cardinal), the 'toda X' single-feminine-weekday recurrence head (kills the double-'às'), the suffix-ellipsis weekday list ('às segundas, quartas e sextas-feiras'), 'na 2ª segunda-feira' (ordinal digit on the weekday-name collision), the dropped comma before 'e', and the ordinal 'dia 1º' for the 1st of the month. Objective gates (round-trip, fuzz dropped-value detector, both-side OR-scope, cRonstrue pt_BR reference) plus the panel gate beta.",
|
|
6
|
+
"note": "BETA — model-validated: sibling-derived from es (a proven structure/style anchor), TDD-green over a blind-panel-reviewed pt-BR corpus, and clean on the corpus-independent mechanical gates (fuzz, round-trip, OR-scope, cRonstrue pt_BR). Two residuals are inherited unchanged from the es donor and are NOT pt regressions (tracked as a joint es+pt follow-up; notes.md §Residuals): (1) the hour-window overlap in '* 2/4,18-20 * * *' (hour 18 named twice — the rendering-clarity artifact, no value dropped); (2) the OR DOW-arm 'e' bracketing in '… ou de segunda a sexta-feira e aos domingos'. The 'short' style switches spelled numbers to digits but pt name abbreviations are not yet implemented (same residue as es). Graduates to stable only on fluent-pt human review. pt-PT is a future dialect axis (notes.md §Dialect axis); no regional dialect ships yet.",
|
|
7
|
+
"dialects": {}
|
|
8
|
+
}
|
package/src/lang/zh/index.ts
CHANGED
|
@@ -25,6 +25,40 @@ type StepSegment = Extract<Segment, {kind: 'step'}>;
|
|
|
25
25
|
const UNITS = {hour: '小时', minute: '分钟', second: '秒'};
|
|
26
26
|
const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
|
27
27
|
|
|
28
|
+
// Simplified → Traditional (zh-Hant) Han glyph map. Schedule prose differs
|
|
29
|
+
// between the two scripts only by character form — within this domain every
|
|
30
|
+
// Simplified glyph that has a Traditional form maps 1:1 with no context
|
|
31
|
+
// sensitivity — so the Traditional variant is the reviewed Simplified output
|
|
32
|
+
// with this map applied at the render boundary, NOT a second word table that
|
|
33
|
+
// would duplicate the renderer's logic. The Taiwan-standard form is chosen for
|
|
34
|
+
// each glyph (週 for week, 點/時/鐘/個/數/單/雙/後/間/從/內); 啟 (not the 啓
|
|
35
|
+
// variant) for 啟動. Two whole-word choices are kept faithful to the 1:1 map
|
|
36
|
+
// and flagged for native review in notes.md: 運行時間 (a Taiwan-native may say
|
|
37
|
+
// 執行時間) and 表達式 (Taiwan tech register may prefer 運算式 / 表示式).
|
|
38
|
+
const HANT: {[glyph: string]: string} = {
|
|
39
|
+
个: '個', 从: '從', 内: '內', 别: '別', 动: '動', 单: '單', 双: '雙',
|
|
40
|
+
后: '後', 启: '啟', 周: '週', 数: '數', 无: '無', 时: '時', 点: '點',
|
|
41
|
+
统: '統', 识: '識', 达: '達', 运: '運', 钟: '鐘', 间: '間'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Apply the Traditional glyph map to a finished Simplified string. The default
|
|
45
|
+
// Simplified (zh / zh-Hans) variant returns the input untouched, so its output
|
|
46
|
+
// is byte-identical to before this variant existed.
|
|
47
|
+
function toVariant(text: string, variant: ChineseStyle['variant']): string {
|
|
48
|
+
if (variant !== 'Hant') {
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return Array.from(text, (glyph) => HANT[glyph] ?? glyph).join('');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The variant the most recent `options()` call resolved. `cronli5()` always
|
|
56
|
+
// calls `options()` before reading `reboot`/`fallback` or invoking `sentence`,
|
|
57
|
+
// none of which receive `opts`; this lets those contract-fixed members honor
|
|
58
|
+
// the dialect without changing the shared Language contract. The library is
|
|
59
|
+
// synchronous and single render per call, so a module-private latch is safe.
|
|
60
|
+
let activeVariant: ChineseStyle['variant'] = 'Hans';
|
|
61
|
+
|
|
28
62
|
// "A、B和C" — enumerate with 、 and join the final item with 和.
|
|
29
63
|
function joinAnd(items: string[]): string {
|
|
30
64
|
if (items.length < 2) {
|
|
@@ -67,7 +101,7 @@ function renderStride(stride: Stride): string {
|
|
|
67
101
|
const {interval, start, last, cycle, unit, mark, anchor} = stride;
|
|
68
102
|
const lead = anchor + '从' + start + mark + '起' + cadence(interval, unit);
|
|
69
103
|
|
|
70
|
-
return chooseStride({start, interval, cycle}, {
|
|
104
|
+
return chooseStride({start, interval, last, cycle}, {
|
|
71
105
|
bare: () => cadence(interval, unit),
|
|
72
106
|
offset: () => lead,
|
|
73
107
|
bounded: () => lead + ',至' + last + mark
|
|
@@ -1405,6 +1439,13 @@ function hourCadenceApplies(schedule: Schedule): boolean {
|
|
|
1405
1439
|
}
|
|
1406
1440
|
|
|
1407
1441
|
function describe(schedule: Schedule, opts: Opts): string {
|
|
1442
|
+
return toVariant(describeHans(schedule, opts), opts.style.variant);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// The Simplified rendering of a schedule; `describe` maps it to the active
|
|
1446
|
+
// variant. The body owns every Simplified glyph; the variant is applied once,
|
|
1447
|
+
// at the boundary, so no emit point has to know which script it is writing.
|
|
1448
|
+
function describeHans(schedule: Schedule, opts: Opts): string {
|
|
1408
1449
|
const {kind} = schedule.plan;
|
|
1409
1450
|
const core = render(schedule, schedule.plan, opts);
|
|
1410
1451
|
let composed = core;
|
|
@@ -1453,22 +1494,36 @@ function describe(schedule: Schedule, opts: Opts): string {
|
|
|
1453
1494
|
function normalizeOptions(options?: Cronli5Options): Opts {
|
|
1454
1495
|
options = options || {};
|
|
1455
1496
|
|
|
1497
|
+
const style = resolveDialect(options.dialect);
|
|
1498
|
+
|
|
1499
|
+
// `cronli5()` reads `reboot`/`fallback` and calls `sentence` without `opts`;
|
|
1500
|
+
// latch the variant here (always called first) so they can honor the dialect.
|
|
1501
|
+
activeVariant = style.variant;
|
|
1502
|
+
|
|
1456
1503
|
return {
|
|
1457
1504
|
ampm: typeof options.ampm === 'boolean' ? options.ampm : false,
|
|
1458
1505
|
lenient: !!options.lenient,
|
|
1459
1506
|
seconds: !!options.seconds,
|
|
1460
1507
|
short: !!options.short,
|
|
1461
|
-
style
|
|
1508
|
+
style,
|
|
1462
1509
|
years: !!options.years
|
|
1463
1510
|
};
|
|
1464
1511
|
}
|
|
1465
1512
|
|
|
1466
1513
|
const zh: Language<ChineseStyle> = {
|
|
1467
1514
|
describe,
|
|
1468
|
-
fallback
|
|
1515
|
+
// `reboot`/`fallback` are contract-fixed strings the core reads without
|
|
1516
|
+
// `opts`; getters honor the variant `options()` latched, keeping the shared
|
|
1517
|
+
// Language contract unchanged while the Traditional dialect still applies.
|
|
1518
|
+
get fallback(): string {
|
|
1519
|
+
return toVariant('无法识别的 cron 表达式', activeVariant);
|
|
1520
|
+
},
|
|
1469
1521
|
options: normalizeOptions,
|
|
1470
|
-
reboot:
|
|
1471
|
-
|
|
1522
|
+
get reboot(): string {
|
|
1523
|
+
return toVariant('系统启动时', activeVariant);
|
|
1524
|
+
},
|
|
1525
|
+
sentence: (description) =>
|
|
1526
|
+
toVariant('运行时间:', activeVariant) + description + '。'
|
|
1472
1527
|
};
|
|
1473
1528
|
|
|
1474
1529
|
export default zh;
|
package/src/lang/zh/notes.md
CHANGED
|
@@ -22,9 +22,21 @@ conventions (this doc) → coverage-spec pattern set → panel-validated
|
|
|
22
22
|
for duration vs 分 for clock position (never swapped); **每天** not 每日;
|
|
23
23
|
suppress the numeral 1 (每分钟, not 每1分钟); no 第 before day/month numbers;
|
|
24
24
|
no redundant 每.
|
|
25
|
-
- **Simplified (zh-Hans) is the default; Traditional (zh-Hant) is a
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
- **Simplified (zh-Hans) is the default; Traditional (zh-Hant) is a within-zh
|
|
26
|
+
variant** selected by the `dialect` option, NOT a separate top-level locale.
|
|
27
|
+
A separate `zh-Hant` module would have to duplicate or import zh's assembly
|
|
28
|
+
logic, both forbidden (a language never imports another language — see
|
|
29
|
+
docs/i18n-design.md; only the core is shared). Within this domain the two
|
|
30
|
+
scripts differ only by character form (no register-level grammar split), so
|
|
31
|
+
the variant is the reviewed Simplified output with a 1:1 Han glyph map applied
|
|
32
|
+
at the render boundary (`toVariant` in `index.ts`:
|
|
33
|
+
時/鐘/點/週/個/數/單/雙/後/間/從/內/無/識/別/啟/統/達/運). It ships
|
|
34
|
+
**experimental** — a model-drafted glyph/register mapping, not yet validated
|
|
35
|
+
by a Traditional-native or blind Hant panel (the same gate that graduates zh).
|
|
36
|
+
Two whole-word choices are flagged for native review: `運行時間` (a
|
|
37
|
+
Taiwan-native may say `執行時間`) and `表達式` (Taiwan tech register may prefer
|
|
38
|
+
`運算式` / `表示式`); both are widely-accepted, and the faithful 1:1 map is
|
|
39
|
+
kept so the variant stays a pure transliteration of the reviewed Hans oracle.
|
|
28
40
|
- **Confinement uses a frame, never juxtaposed cadences:** 在9点至17点之间,
|
|
29
41
|
每15分钟 — the 在…之间 frame binds the cadence to the window (the same
|
|
30
42
|
confinement-vs-juxtaposition rule as the other languages).
|
|
@@ -95,7 +107,7 @@ every field value preserved — `npm run fuzz zh` is clean):
|
|
|
95
107
|
|---|---|---|---|
|
|
96
108
|
| `numerals` | `'arabic'` / `'chinese'` | arabic | 9点 vs 九点 |
|
|
97
109
|
| `clock` | `'24h'` / `'12h'` | 24h | 14点 vs 下午2点 (maps to today's `ampm`) |
|
|
98
|
-
| `
|
|
110
|
+
| `dialect` | `'zh-Hans'` / `'zh-Hant'` | zh-Hans | Simplified vs Traditional Han glyph form (within-zh variant; experimental) |
|
|
99
111
|
| `quarterHour` | bool | false | enable 半 / 一刻 / 三刻 (default is explicit 分) |
|
|
100
112
|
| `useHao` | bool | false | 号 vs 日 |
|
|
101
113
|
|
package/src/lang/zh/status.json
CHANGED
|
@@ -3,5 +3,14 @@
|
|
|
3
3
|
"status": "beta",
|
|
4
4
|
"humanReview": null,
|
|
5
5
|
"modelReview": "blind 3-persona Sonnet style panel + author/audit corpus workflow (2026-06-20); npm run fuzz zh clean (0 throws / degenerate / missing-value)",
|
|
6
|
-
"note": "BETA — model-validated by a blind 3-persona Sonnet panel (src/lang/zh/notes.md), corpus authored via an author/audit/fix workflow and converged to the renderer's canonical forms. Graduates to stable only on fluent-human review."
|
|
6
|
+
"note": "BETA — model-validated by a blind 3-persona Sonnet panel (src/lang/zh/notes.md), corpus authored via an author/audit/fix workflow and converged to the renderer's canonical forms. Graduates to stable only on fluent-human review.",
|
|
7
|
+
"variants": {
|
|
8
|
+
"zh-Hant": {
|
|
9
|
+
"name": "Chinese (Mandarin, Traditional)",
|
|
10
|
+
"status": "experimental",
|
|
11
|
+
"humanReview": null,
|
|
12
|
+
"modelReview": null,
|
|
13
|
+
"note": "EXPERIMENTAL — a within-zh variant (the reviewed Simplified output with a 1:1 Han glyph map applied at the render boundary, selected by {dialect: 'zh-Hant'}), NOT a separate locale. Model-drafted glyph/register mapping, not yet validated by a Traditional-native or blind Hant panel. Graduates to beta on a Traditional-native / blind Hant review (the same gate model as Simplified zh). Whole-word choices flagged for native review: 運行時間 (vs 執行時間), 表達式 (vs 運算式 / 表示式)."
|
|
14
|
+
}
|
|
15
|
+
}
|
|
7
16
|
}
|
package/types/core/cadence.d.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Cronli5Options } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* French's own resolved style shape: a separator and the spacing of the `h`
|
|
4
|
+
* clock mark. fr is 24-hour only, so there is no `ampm`/`meridiem` axis.
|
|
5
|
+
*/
|
|
6
|
+
export interface FrenchStyle {
|
|
7
|
+
sep: string;
|
|
8
|
+
unspaced: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function resolveDialect(dialect: Cronli5Options['dialect']): FrenchStyle;
|
|
11
|
+
export { resolveDialect };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Cronli5Options } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Portuguese's own resolved style shape has a separator,
|
|
4
|
+
* clock default, meridiem form, and `h` suffix.
|
|
5
|
+
*/
|
|
6
|
+
export interface PortugueseStyle {
|
|
7
|
+
ampm: boolean;
|
|
8
|
+
hSuffix: boolean;
|
|
9
|
+
meridiem: 'descriptors' | 'english';
|
|
10
|
+
sep: string;
|
|
11
|
+
}
|
|
12
|
+
declare function resolveDialect(dialect: Cronli5Options['dialect']): PortugueseStyle;
|
|
13
|
+
export { resolveDialect };
|