chronolizer 0.1.0 → 0.3.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.
Files changed (62) hide show
  1. package/README.md +184 -119
  2. package/dist/ast/constructors.d.mts +135 -0
  3. package/dist/ast/constructors.mjs +25 -0
  4. package/dist/ast/fold.d.mts +13 -0
  5. package/dist/ast/fold.mjs +58 -0
  6. package/dist/ast/normalize.d.mts +42 -0
  7. package/dist/ast/normalize.mjs +34 -0
  8. package/dist/ast/schemas.d.mts +80 -0
  9. package/dist/ast/schemas.mjs +65 -0
  10. package/dist/filter/codec.d.mts +92 -0
  11. package/dist/filter/codec.mjs +48 -0
  12. package/dist/filter/errors.d.mts +14 -0
  13. package/dist/filter/errors.mjs +10 -0
  14. package/dist/filter/expression.d.mts +8 -0
  15. package/dist/filter/expression.mjs +76 -0
  16. package/dist/filter/schema.d.mts +13 -0
  17. package/dist/filter/schema.mjs +11 -0
  18. package/dist/filter/transformation.d.mts +37 -0
  19. package/dist/filter/transformation.mjs +20 -0
  20. package/dist/index.d.mts +22 -969
  21. package/dist/index.mjs +22 -2305
  22. package/dist/language/errors.d.mts +38 -0
  23. package/dist/language/errors.mjs +30 -0
  24. package/dist/language/model.d.mts +239 -0
  25. package/dist/language/model.mjs +50 -0
  26. package/dist/language/registry.d.mts +17 -0
  27. package/dist/language/registry.mjs +116 -0
  28. package/dist/locales/cs.d.mts +10 -0
  29. package/dist/locales/cs.mjs +746 -0
  30. package/dist/locales/de.d.mts +10 -0
  31. package/dist/locales/de.mjs +1039 -0
  32. package/dist/locales/en.d.mts +10 -0
  33. package/dist/locales/en.mjs +598 -0
  34. package/dist/locales/es.d.mts +10 -0
  35. package/dist/locales/es.mjs +908 -0
  36. package/dist/locales/fr.d.mts +10 -0
  37. package/dist/locales/fr.mjs +901 -0
  38. package/dist/locales/nl.d.mts +10 -0
  39. package/dist/locales/nl.mjs +791 -0
  40. package/dist/locales/pl.d.mts +10 -0
  41. package/dist/locales/pl.mjs +885 -0
  42. package/dist/locales/shared.mjs +395 -0
  43. package/dist/locales/tr.d.mts +10 -0
  44. package/dist/locales/tr.mjs +725 -0
  45. package/dist/natural/correction.d.mts +13 -0
  46. package/dist/natural/correction.mjs +73 -0
  47. package/dist/natural/format.d.mts +47 -0
  48. package/dist/natural/format.mjs +14 -0
  49. package/dist/natural/parse.d.mts +99 -0
  50. package/dist/natural/parse.mjs +66 -0
  51. package/dist/natural/policy.mjs +6 -0
  52. package/dist/natural/suggest.d.mts +53 -0
  53. package/dist/natural/suggest.mjs +24 -0
  54. package/dist/natural/suggestion.d.mts +4 -0
  55. package/dist/natural/suggestion.mjs +79 -0
  56. package/dist/natural/text.d.mts +5 -0
  57. package/dist/natural/text.mjs +5 -0
  58. package/dist/resolve/resolve.d.mts +79 -0
  59. package/dist/resolve/resolve.mjs +58 -0
  60. package/dist/resolve/schema.d.mts +59 -0
  61. package/dist/resolve/schema.mjs +28 -0
  62. package/package.json +15 -3
@@ -0,0 +1,791 @@
1
+ import { isIsoDate } from "../ast/schemas.mjs";
2
+ import { BaseLanguageContribution } from "../language/model.mjs";
3
+ import { normalizeNaturalText } from "../natural/text.mjs";
4
+ import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
5
+ import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
6
+ import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount, prefixNaturalPhrases } from "../natural/suggestion.mjs";
7
+ import { absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodDay, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
8
+ import { Effect, Option, String as String$1 } from "effect";
9
+ //#region src/locales/nl.ts
10
+ const months = [
11
+ "januari",
12
+ "februari",
13
+ "maart",
14
+ "april",
15
+ "mei",
16
+ "juni",
17
+ "juli",
18
+ "augustus",
19
+ "september",
20
+ "oktober",
21
+ "november",
22
+ "december"
23
+ ];
24
+ const monthAbbreviations = [
25
+ ["jan"],
26
+ ["feb"],
27
+ ["mar", "mrt"],
28
+ ["apr"],
29
+ [],
30
+ ["jun"],
31
+ ["jul"],
32
+ ["aug"],
33
+ ["sep", "sept"],
34
+ ["okt"],
35
+ ["nov"],
36
+ ["dec"]
37
+ ];
38
+ const units = [
39
+ {
40
+ unit: "day",
41
+ singular: "dag",
42
+ plural: "dagen",
43
+ article: "de",
44
+ current: "vandaag",
45
+ previous: "gisteren",
46
+ next: "morgen",
47
+ toDate: "dag tot nu toe",
48
+ remaining: "rest van de dag"
49
+ },
50
+ {
51
+ unit: "week",
52
+ singular: "week",
53
+ plural: "weken",
54
+ article: "de",
55
+ current: "deze week",
56
+ previous: "vorige week",
57
+ next: "volgende week",
58
+ toDate: "week tot nu toe",
59
+ remaining: "rest van de week"
60
+ },
61
+ {
62
+ unit: "month",
63
+ singular: "maand",
64
+ plural: "maanden",
65
+ article: "de",
66
+ current: "deze maand",
67
+ previous: "vorige maand",
68
+ next: "volgende maand",
69
+ toDate: "maand tot nu toe",
70
+ remaining: "rest van de maand"
71
+ },
72
+ {
73
+ unit: "quarter",
74
+ singular: "kwartaal",
75
+ plural: "kwartalen",
76
+ article: "het",
77
+ current: "dit kwartaal",
78
+ previous: "vorig kwartaal",
79
+ next: "volgend kwartaal",
80
+ toDate: "kwartaal tot nu toe",
81
+ remaining: "rest van het kwartaal"
82
+ },
83
+ {
84
+ unit: "year",
85
+ singular: "jaar",
86
+ plural: "jaar",
87
+ article: "het",
88
+ current: "dit jaar",
89
+ previous: "vorig jaar",
90
+ next: "volgend jaar",
91
+ toDate: "jaar tot nu toe",
92
+ remaining: "rest van het jaar"
93
+ }
94
+ ];
95
+ const afterVan = (period) => period.startsWith("van ") ? period.slice(4) : period;
96
+ const unitAliases = [
97
+ [
98
+ "dag",
99
+ "day",
100
+ "singular"
101
+ ],
102
+ [
103
+ "dagen",
104
+ "day",
105
+ "plural"
106
+ ],
107
+ [
108
+ "week",
109
+ "week",
110
+ "singular"
111
+ ],
112
+ [
113
+ "weken",
114
+ "week",
115
+ "plural"
116
+ ],
117
+ [
118
+ "maand",
119
+ "month",
120
+ "singular"
121
+ ],
122
+ [
123
+ "maanden",
124
+ "month",
125
+ "plural"
126
+ ],
127
+ [
128
+ "kwartaal",
129
+ "quarter",
130
+ "singular"
131
+ ],
132
+ [
133
+ "kwartalen",
134
+ "quarter",
135
+ "plural"
136
+ ],
137
+ [
138
+ "jaar",
139
+ "year",
140
+ "both"
141
+ ],
142
+ [
143
+ "jaren",
144
+ "year",
145
+ "plural"
146
+ ]
147
+ ];
148
+ const periodAliases = [
149
+ ...units.flatMap((entry) => [
150
+ [
151
+ entry.current,
152
+ entry.unit,
153
+ 0,
154
+ entry.current
155
+ ],
156
+ [
157
+ entry.previous,
158
+ entry.unit,
159
+ -1,
160
+ entry.previous
161
+ ],
162
+ [
163
+ entry.next,
164
+ entry.unit,
165
+ 1,
166
+ entry.next
167
+ ]
168
+ ]),
169
+ [
170
+ "huidige week",
171
+ "week",
172
+ 0,
173
+ "deze week"
174
+ ],
175
+ [
176
+ "afgelopen week",
177
+ "week",
178
+ -1,
179
+ "vorige week"
180
+ ],
181
+ [
182
+ "komende week",
183
+ "week",
184
+ 1,
185
+ "volgende week"
186
+ ],
187
+ [
188
+ "huidige maand",
189
+ "month",
190
+ 0,
191
+ "deze maand"
192
+ ],
193
+ [
194
+ "afgelopen maand",
195
+ "month",
196
+ -1,
197
+ "vorige maand"
198
+ ],
199
+ [
200
+ "aankomende maand",
201
+ "month",
202
+ 1,
203
+ "volgende maand"
204
+ ],
205
+ [
206
+ "huidig kwartaal",
207
+ "quarter",
208
+ 0,
209
+ "dit kwartaal"
210
+ ],
211
+ [
212
+ "afgelopen kwartaal",
213
+ "quarter",
214
+ -1,
215
+ "vorig kwartaal"
216
+ ],
217
+ [
218
+ "komend kwartaal",
219
+ "quarter",
220
+ 1,
221
+ "volgend kwartaal"
222
+ ],
223
+ [
224
+ "huidig jaar",
225
+ "year",
226
+ 0,
227
+ "dit jaar"
228
+ ],
229
+ [
230
+ "afgelopen jaar",
231
+ "year",
232
+ -1,
233
+ "vorig jaar"
234
+ ],
235
+ [
236
+ "komend jaar",
237
+ "year",
238
+ 1,
239
+ "volgend jaar"
240
+ ],
241
+ [
242
+ "eergisteren",
243
+ "day",
244
+ -2,
245
+ "eergisteren"
246
+ ],
247
+ [
248
+ "overmorgen",
249
+ "day",
250
+ 2,
251
+ "overmorgen"
252
+ ],
253
+ [
254
+ "de week voor de vorige",
255
+ "week",
256
+ -2,
257
+ "de week voor de vorige"
258
+ ],
259
+ [
260
+ "de week na de volgende",
261
+ "week",
262
+ 2,
263
+ "de week na de volgende"
264
+ ],
265
+ [
266
+ "de maand voor de vorige",
267
+ "month",
268
+ -2,
269
+ "de maand voor de vorige"
270
+ ],
271
+ [
272
+ "de maand na de volgende",
273
+ "month",
274
+ 2,
275
+ "de maand na de volgende"
276
+ ],
277
+ [
278
+ "het kwartaal voor het vorige",
279
+ "quarter",
280
+ -2,
281
+ "het kwartaal voor het vorige"
282
+ ],
283
+ [
284
+ "het kwartaal na het volgende",
285
+ "quarter",
286
+ 2,
287
+ "het kwartaal na het volgende"
288
+ ],
289
+ [
290
+ "het jaar voor het vorige",
291
+ "year",
292
+ -2,
293
+ "het jaar voor het vorige"
294
+ ],
295
+ [
296
+ "het jaar na het volgende",
297
+ "year",
298
+ 2,
299
+ "het jaar na het volgende"
300
+ ]
301
+ ];
302
+ const periodArticle = {
303
+ day: "van de dag",
304
+ week: "van de week",
305
+ month: "van de maand",
306
+ quarter: "van het kwartaal",
307
+ year: "van het jaar"
308
+ };
309
+ const toDatePhrases = units.flatMap((entry) => {
310
+ const yearAliases = entry.unit === "year" ? [
311
+ "sinds jaarbegin",
312
+ "vanaf jaarbegin",
313
+ "sinds het begin van dit jaar"
314
+ ] : [];
315
+ return [
316
+ entry.toDate,
317
+ `sinds het begin ${periodArticle[entry.unit]}`,
318
+ `vanaf het begin ${periodArticle[entry.unit]}`,
319
+ `${entry.current} tot nu toe`,
320
+ ...yearAliases
321
+ ].map((phrase) => ({
322
+ entry,
323
+ phrase
324
+ }));
325
+ });
326
+ const remainingPhrases = units.flatMap((entry) => [entry.remaining, `wat over is ${periodArticle[entry.unit]}`].map((phrase) => ({
327
+ entry,
328
+ phrase
329
+ })));
330
+ const relativeYearDirection = (value) => {
331
+ if (value.includes("vorig")) return -1;
332
+ if (value.includes("volgend")) return 1;
333
+ return 0;
334
+ };
335
+ const relativeYearName = (direction) => {
336
+ if (direction < 0) return "vorig jaar";
337
+ if (direction > 0) return "volgend jaar";
338
+ return "dit jaar";
339
+ };
340
+ const monthNumber = (value) => {
341
+ const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
342
+ const full = months.findIndex((month) => month === normalized);
343
+ if (full !== -1) return full + 1;
344
+ const short = monthAbbreviations.findIndex((aliases) => aliases.some((alias) => alias === normalized));
345
+ return short === -1 ? void 0 : short + 1;
346
+ };
347
+ const dateLabel = (day, month, year) => `${day} ${textAt(months, month - 1)} ${year}`;
348
+ const currentDateLabel = (day, month) => `${day} ${textAt(months, month - 1)}`;
349
+ const parseNamedDate = (input) => {
350
+ const named = String$1.match(/^(?:de )?([0-3]?\d)(?:e|ste|de)?(?: van)? ([a-z]+\.?)(?: van)? (\d{4})$/u)(input);
351
+ if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
352
+ const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
353
+ if (Option.isSome(numeric)) {
354
+ const year = validYear(textAt(numeric.value, 3));
355
+ const month = Number(textAt(numeric.value, 2));
356
+ const day = Number(textAt(numeric.value, 1));
357
+ if (year !== void 0 && month >= 1 && month <= 12) {
358
+ const value = isoDate(year, month, day);
359
+ if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
360
+ }
361
+ }
362
+ const current = String$1.match(/^(?:de )?([0-3]?\d)(?:e|ste|de)?(?: van)? ([a-z]+\.?)$/u)(input);
363
+ if (Option.isSome(current)) return namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel);
364
+ const relative = String$1.match(/^(?:de )?([0-3]?\d)(?:e|ste|de)? van (.+)$/u)(input);
365
+ if (Option.isNone(relative)) return Option.none();
366
+ const periodText = textAt(relative.value, 2);
367
+ const alias = periodAliases.find((entry) => entry[0] === periodText && entry[1] === "month");
368
+ if (alias === void 0) return Option.none();
369
+ const day = Number(textAt(relative.value, 1));
370
+ const month = relativePeriod(alias[1], alias[2], alias[3]);
371
+ return periodDay(month, day, `${day} van ${alias[3]}`);
372
+ };
373
+ const quarterNumber = (value) => {
374
+ if ((value.startsWith("q") || value.startsWith("k")) && value.length === 2) return Number(value.slice(1));
375
+ if (value === "1e" || value === "eerste") return 1;
376
+ if (value === "2e" || value === "tweede") return 2;
377
+ if (value === "3e" || value === "derde") return 3;
378
+ return value === "4e" || value === "vierde" ? 4 : void 0;
379
+ };
380
+ const parseQuarter = (input) => {
381
+ const fixed = String$1.match(/^(k[1-4]|q[1-4]|1e|eerste|2e|tweede|3e|derde|4e|vierde)(?: kwartaal)?(?: van)? (\d{4})$/u)(input);
382
+ if (Option.isSome(fixed)) {
383
+ const quarter = quarterNumber(textAt(fixed.value, 1));
384
+ const year = validYear(textAt(fixed.value, 2));
385
+ if (quarter !== void 0 && year !== void 0) return Option.some(fixedQuarterPeriod(year, quarter, `K${quarter} ${year}`));
386
+ }
387
+ const relative = String$1.match(/^(?:het )?(k[1-4]|q[1-4]) (?:van )?(vorig jaar|volgend jaar|dit jaar)$/u)(input);
388
+ if (Option.isSome(relative)) {
389
+ const quarter = quarterNumber(textAt(relative.value, 1));
390
+ const direction = relativeYearDirection(textAt(relative.value, 2));
391
+ if (quarter !== void 0) return Option.some(quarterOfRelativeYear(quarter, direction, `K${quarter} ${relativeYearName(direction)}`));
392
+ }
393
+ const standalone = String$1.match(/^(?:het )?(k[1-4]|q[1-4])$/u)(input);
394
+ if (Option.isNone(standalone)) return Option.none();
395
+ const quarter = quarterNumber(textAt(standalone.value, 1));
396
+ return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `K${quarter}`));
397
+ };
398
+ const parseBasePeriod = (input) => {
399
+ const absoluteDate = absoluteDatePeriod(input, "nl");
400
+ if (Option.isSome(absoluteDate)) return absoluteDate;
401
+ const namedDate = parseNamedDate(input);
402
+ if (Option.isSome(namedDate)) return namedDate;
403
+ const quarter = parseQuarter(input);
404
+ if (Option.isSome(quarter)) return quarter;
405
+ const yearMatch = String$1.match(/^(?:het jaar |jaar )?(\d{4})$/u)(input);
406
+ if (Option.isSome(yearMatch)) {
407
+ const year = validYear(textAt(yearMatch.value, 1));
408
+ if (year !== void 0) return Option.some(fixedYearPeriod(year, String(year)));
409
+ }
410
+ const monthYear = String$1.match(/^([a-z]+\.?)(?: van)? (\d{4})$/u)(input);
411
+ if (Option.isSome(monthYear)) {
412
+ const month = monthNumber(textAt(monthYear.value, 1));
413
+ const year = validYear(textAt(monthYear.value, 2));
414
+ if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
415
+ }
416
+ const relativeMonth = String$1.match(/^([a-z]+\.?)(?: van)? (vorig jaar|volgend jaar|dit jaar)$/u)(input);
417
+ const relativeMonthYearFirst = String$1.match(/^(vorig jaar|volgend jaar|dit jaar) ([a-z]+\.?)$/u)(input);
418
+ const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
419
+ if (Option.isSome(relativeMatch)) {
420
+ const yearFirst = Option.isSome(relativeMonthYearFirst);
421
+ const monthText = textAt(relativeMatch.value, yearFirst ? 2 : 1);
422
+ const yearText = textAt(relativeMatch.value, yearFirst ? 1 : 2);
423
+ const month = monthNumber(monthText);
424
+ const direction = relativeYearDirection(yearText);
425
+ if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${textAt(months, month - 1)} ${relativeYearName(direction)}`));
426
+ }
427
+ const standaloneMonth = monthNumber(input);
428
+ if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, textAt(months, standaloneMonth - 1)));
429
+ const alias = periodAliases.find((entry) => entry[0] === input);
430
+ if (alias !== void 0) return Option.some(relativePeriod(alias[1], alias[2], alias[3]));
431
+ if ([
432
+ "weekend",
433
+ "het weekend",
434
+ "dit weekend"
435
+ ].includes(input)) return Option.some(relativeWeekend(0, "dit weekend"));
436
+ if ([
437
+ "vorig weekend",
438
+ "het vorige weekend",
439
+ "afgelopen weekend"
440
+ ].includes(input)) return Option.some(relativeWeekend(-1, "vorig weekend"));
441
+ if ([
442
+ "volgend weekend",
443
+ "het volgende weekend",
444
+ "komend weekend"
445
+ ].includes(input)) return Option.some(relativeWeekend(1, "volgend weekend"));
446
+ if (input === "het weekend voor het vorige") return Option.some(relativeWeekend(-2, input));
447
+ if (input === "het weekend na het volgende") return Option.some(relativeWeekend(2, input));
448
+ return Option.none();
449
+ };
450
+ const parsePeriod = (input) => {
451
+ const edge = String$1.match(/^(?:het )?(begin|eind|einde)(?: van)? (.+)$/u)(input);
452
+ if (Option.isSome(edge)) {
453
+ const edgeName = textAt(edge.value, 1);
454
+ const period = parseBasePeriod(textAt(edge.value, 2));
455
+ if (Option.isSome(period)) {
456
+ const isEnd = edgeName === "eind" || edgeName === "einde";
457
+ const canonical = `${isEnd ? "eind" : "begin"} van ${period.value.canonical}`;
458
+ return Option.some(isEnd ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
459
+ }
460
+ }
461
+ const wrapper = [
462
+ "gedurende ",
463
+ "in ",
464
+ "heel ",
465
+ "de hele ",
466
+ "het hele "
467
+ ].find((prefix) => input.startsWith(prefix));
468
+ return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
469
+ };
470
+ const countedUnit = (value, amount) => {
471
+ const expected = amount === 1 ? "singular" : "plural";
472
+ const alias = unitAliases.find((entry) => entry[0] === value && (entry[2] === expected || entry[2] === "both"));
473
+ return alias === void 0 ? void 0 : units.find((entry) => entry.unit === alias[1]);
474
+ };
475
+ const countedUnitPattern = "dag|dagen|week|weken|maand|maanden|kwartaal|kwartalen|jaar|jaren";
476
+ const countedPattern = (source) => new RegExp(source.replace("UNIT", countedUnitPattern), "u");
477
+ const calendarPastPattern = countedPattern("^([1-9]\\d*) (UNIT) geleden$");
478
+ const calendarFuturePatterns = [countedPattern("^(?:over|binnen) ([1-9]\\d*) (UNIT)$"), countedPattern("^([1-9]\\d*) (UNIT) later$")];
479
+ const rollingPastPatterns = [countedPattern("^(?:(?:de|in de) )?(?:afgelopen|laatste|vorige) ([1-9]\\d*) (UNIT)$"), countedPattern("^([1-9]\\d*) (?:afgelopen|laatste|vorige) (UNIT)$")];
480
+ const rollingFuturePatterns = [countedPattern("^(?:(?:de|in de|binnen de) )?(?:komende|volgende|aankomende) ([1-9]\\d*) (UNIT)$"), countedPattern("^([1-9]\\d*) (?:komende|volgende|aankomende) (UNIT)$")];
481
+ const rollingSincePattern = countedPattern("^(?:sinds|gedurende) ([1-9]\\d*) (UNIT)$");
482
+ const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
483
+ const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
484
+ const singularRollingCanonical = (entry, future) => future ? `vanaf nu gedurende een ${entry.singular}` : `sinds een ${entry.singular}`;
485
+ const singularRollingPhrases = units.flatMap((entry) => [
486
+ {
487
+ phrase: `${entry.article} afgelopen ${entry.singular}`,
488
+ entry,
489
+ future: false
490
+ },
491
+ {
492
+ phrase: `${entry.article} laatste ${entry.singular}`,
493
+ entry,
494
+ future: false
495
+ },
496
+ {
497
+ phrase: `sinds een ${entry.singular}`,
498
+ entry,
499
+ future: false
500
+ },
501
+ {
502
+ phrase: `gedurende ${entry.article} komende ${entry.singular}`,
503
+ entry,
504
+ future: true
505
+ },
506
+ {
507
+ phrase: `vanaf nu gedurende een ${entry.singular}`,
508
+ entry,
509
+ future: true
510
+ }
511
+ ]);
512
+ const singularCalendarOffsets = units.flatMap((entry) => [
513
+ {
514
+ phrase: `een ${entry.singular} geleden`,
515
+ entry,
516
+ direction: -1
517
+ },
518
+ {
519
+ phrase: `over een ${entry.singular}`,
520
+ entry,
521
+ direction: 1
522
+ },
523
+ {
524
+ phrase: `binnen een ${entry.singular}`,
525
+ entry,
526
+ direction: 1
527
+ }
528
+ ]);
529
+ const parseCalendarOffset = (input) => {
530
+ const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
531
+ if (singular !== void 0) return Option.some(candidate(periodRange(relativePeriod(singular.entry.unit, singular.direction, singular.phrase)), singular.phrase));
532
+ const past = String$1.match(calendarPastPattern)(input);
533
+ const future = firstPatternMatch(input, calendarFuturePatterns);
534
+ const match = Option.firstSomeOf([past, future]);
535
+ if (Option.isNone(match)) return Option.none();
536
+ const amount = parseTrailingCount(textAt(match.value, 1));
537
+ if (Option.isNone(amount)) return Option.none();
538
+ const entry = countedUnit(textAt(match.value, 2), amount.value);
539
+ if (entry === void 0) return Option.none();
540
+ const direction = Option.isSome(past) ? -amount.value : amount.value;
541
+ const noun = amount.value === 1 ? entry.singular : entry.plural;
542
+ const canonical = direction < 0 ? `${amount.value} ${noun} geleden` : `over ${amount.value} ${noun}`;
543
+ return Option.some(candidate(periodRange(relativePeriod(entry.unit, direction, canonical)), canonical));
544
+ };
545
+ const parseRollingPeriod = (input) => {
546
+ const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
547
+ if (singular !== void 0) {
548
+ const range = singular.future ? futureRange(1, singular.entry.unit) : trailingRange(1, singular.entry.unit);
549
+ return Option.some(candidate(range, singularRollingCanonical(singular.entry, singular.future)));
550
+ }
551
+ const since = String$1.match(rollingSincePattern)(input);
552
+ const past = firstPatternMatch(input, rollingPastPatterns);
553
+ const bare = String$1.match(rollingBarePattern)(input);
554
+ const future = firstPatternMatch(input, rollingFuturePatterns);
555
+ const match = Option.firstSomeOf([
556
+ since,
557
+ past,
558
+ bare,
559
+ future
560
+ ]);
561
+ if (Option.isNone(match)) return Option.none();
562
+ const amount = parseTrailingCount(textAt(match.value, 1));
563
+ if (Option.isNone(amount)) return Option.none();
564
+ const entry = countedUnit(textAt(match.value, 2), amount.value);
565
+ if (entry === void 0) return Option.none();
566
+ const isFuture = Option.isSome(future);
567
+ const range = isFuture ? futureRange(amount.value, entry.unit) : trailingRange(amount.value, entry.unit);
568
+ if (amount.value === 1) return Option.some(candidate(range, singularRollingCanonical(entry, isFuture)));
569
+ const modifier = isFuture ? "komende" : "afgelopen";
570
+ return Option.some(candidate(range, `de ${modifier} ${amount.value} ${entry.plural}`));
571
+ };
572
+ const parseElidedDateRange = (input) => {
573
+ const joined = String$1.match(/^(?:van ([0-3]?\d) tot(?: en met)?|tussen (?:de )?([0-3]?\d)(?:e)? en(?: de)?) ([0-3]?\d)(?:e)? (.+)$/u)(input);
574
+ const dashed = String$1.match(/^([0-3]?\d)[–—-]([0-3]?\d) (.+)$/u)(input);
575
+ const match = Option.firstSomeOf([joined, dashed]);
576
+ if (Option.isNone(match)) return Option.none();
577
+ const isJoined = Option.isSome(joined);
578
+ const lowerDay = textAt(match.value, 1) || textAt(match.value, 2);
579
+ const upperDay = textAt(match.value, isJoined ? 3 : 2);
580
+ const period = afterVan(textAt(match.value, isJoined ? 4 : 3));
581
+ const suffix = periodAliases.some((entry) => entry[0] === period && entry[1] === "month") ? `van ${period}` : period;
582
+ return joinedPeriodCandidate(`van ${lowerDay} ${suffix} tot en met ${upperDay} ${suffix}`, [["van ", " tot en met "]], parsePeriod, (lower, upper) => `van ${lower} tot en met ${upper}`);
583
+ };
584
+ const boundaryCandidate = (input) => openBoundaryCandidate(input, [
585
+ ["tot voor ", "before"],
586
+ ["tot het begin van ", "before"],
587
+ ["tot en met ", "through"],
588
+ ["vanaf ", "since"],
589
+ ["sinds ", "since"],
590
+ ["voor ", "before"],
591
+ ["tot ", "through"],
592
+ ["na ", "after"]
593
+ ], parsePeriod);
594
+ const parseDutch = (input) => {
595
+ const remaining = remainingPhrases.find((entry) => entry.phrase === input);
596
+ if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry.unit), remaining.entry.remaining));
597
+ if ([
598
+ "tot vandaag",
599
+ "tot nu",
600
+ "tot nu toe"
601
+ ].includes(input)) return Option.some(candidate(untilNowRange(), "tot nu toe"));
602
+ if ([
603
+ "vanaf nu",
604
+ "sinds nu",
605
+ "voortaan"
606
+ ].includes(input)) return Option.some(candidate(fromNowRange(), "vanaf nu"));
607
+ const offset = parseCalendarOffset(input);
608
+ if (Option.isSome(offset)) return offset;
609
+ const rolling = parseRollingPeriod(input);
610
+ if (Option.isSome(rolling)) return rolling;
611
+ const toDate = toDatePhrases.find((entry) => entry.phrase === input);
612
+ if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
613
+ const elided = parseElidedDateRange(input);
614
+ if (Option.isSome(elided)) return elided;
615
+ const nowBounded = joinedNowCandidate(input, [
616
+ ["vanaf ", " tot nu toe"],
617
+ ["sinds ", " tot nu toe"],
618
+ ["tussen ", " en vandaag"]
619
+ ], [
620
+ "vanaf nu tot en met ",
621
+ "van vandaag tot en met ",
622
+ "tussen vandaag en "
623
+ ], parsePeriod, (period) => `vanaf ${period} tot nu toe`, (period) => `vanaf nu tot en met ${period}`);
624
+ if (Option.isSome(nowBounded)) return nowBounded;
625
+ const bounded = joinedPeriodCandidate(input, [
626
+ ["van ", " tot en met "],
627
+ ["van ", " tot "],
628
+ ["tussen ", " en "],
629
+ ["", " - "],
630
+ ["", " – "],
631
+ ["", " — "],
632
+ ["", " tot en met "],
633
+ ["", " tot "]
634
+ ], parsePeriod, (lower, upper) => `van ${lower} tot en met ${upper}`);
635
+ if (Option.isSome(bounded)) return bounded;
636
+ const boundary = boundaryCandidate(input);
637
+ if (Option.isSome(boundary)) return boundary;
638
+ return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
639
+ };
640
+ const weekendPhrases = [
641
+ "dit weekend",
642
+ "vorig weekend",
643
+ "volgend weekend",
644
+ "het weekend voor het vorige",
645
+ "het weekend na het volgende"
646
+ ];
647
+ const edgePeriodPhrases = [
648
+ ...units.filter((entry) => entry.unit !== "day").flatMap((entry) => [
649
+ entry.current,
650
+ entry.previous,
651
+ entry.next
652
+ ]),
653
+ ...weekendPhrases,
654
+ ...months
655
+ ].flatMap((period) => [`begin van ${period}`, `eind van ${period}`]);
656
+ const staticPeriodPhrases = [
657
+ ...periodAliases.map((entry) => entry[0]),
658
+ ...weekendPhrases,
659
+ ...edgePeriodPhrases,
660
+ ...[
661
+ 1,
662
+ 2,
663
+ 3,
664
+ 4
665
+ ].flatMap((quarter) => [
666
+ `k${quarter}`,
667
+ `k${quarter} dit jaar`,
668
+ `k${quarter} vorig jaar`,
669
+ `k${quarter} volgend jaar`
670
+ ]),
671
+ ...months.flatMap((month) => [
672
+ month,
673
+ `${month} vorig jaar`,
674
+ `${month} volgend jaar`
675
+ ])
676
+ ];
677
+ const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
678
+ const boundaryPrefixes = [
679
+ "vanaf ",
680
+ "sinds ",
681
+ "voor ",
682
+ "tot en met ",
683
+ "na "
684
+ ];
685
+ const countedSuggestions = (input) => {
686
+ const amount = naturalCount(input);
687
+ if (amount === void 0) return [];
688
+ return units.flatMap((entry) => {
689
+ const noun = amount === 1 ? entry.singular : entry.plural;
690
+ return [
691
+ `de afgelopen ${amount} ${noun}`,
692
+ `afgelopen ${amount} ${noun}`,
693
+ `de laatste ${amount} ${noun}`,
694
+ `${amount} ${noun}`,
695
+ `de komende ${amount} ${noun}`,
696
+ `komende ${amount} ${noun}`,
697
+ `${amount} ${noun} geleden`,
698
+ `over ${amount} ${noun}`
699
+ ];
700
+ });
701
+ };
702
+ const dutchSuggestionPhrases = [
703
+ ...units.map((entry) => entry.toDate),
704
+ ...units.map((entry) => entry.remaining),
705
+ ...singularRollingPhrases.map((entry) => entry.phrase),
706
+ ...singularCalendarOffsets.map((entry) => entry.phrase),
707
+ ...staticPeriodPhrases,
708
+ ...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
709
+ "tot nu toe",
710
+ "vanaf nu"
711
+ ];
712
+ const suggestDutch = (input, limit) => {
713
+ const fixed = fixedCalendarPeriodPhrases(input, months).map((phrase) => phrase.startsWith("q") ? `k${phrase.slice(1)}` : phrase);
714
+ return completeNaturalPhrases(input, [
715
+ ...dutchSuggestionPhrases,
716
+ ...fixed,
717
+ ...prefixNaturalPhrases(fixed, boundaryPrefixes),
718
+ ...countedSuggestions(input)
719
+ ], limit);
720
+ };
721
+ const renderDutch = (range) => {
722
+ const offset = calendarPeriodOffset(range);
723
+ if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
724
+ const entry = units.find((unit) => unit.unit === offset.value.unit);
725
+ if (entry !== void 0) {
726
+ const amount = Math.abs(offset.value.amount);
727
+ const noun = amount === 1 ? entry.singular : entry.plural;
728
+ return Option.some(offset.value.amount < 0 ? `${amount} ${noun} geleden` : `over ${amount} ${noun}`);
729
+ }
730
+ }
731
+ const future = futurePeriod(range);
732
+ if (Option.isSome(future)) {
733
+ const entry = units.find((unit) => unit.unit === future.value.unit);
734
+ if (entry !== void 0) return Option.some(future.value.amount === 1 ? singularRollingCanonical(entry, true) : `de komende ${future.value.amount} ${entry.plural}`);
735
+ }
736
+ const trailing = trailingPeriod(range);
737
+ if (Option.isSome(trailing)) {
738
+ const entry = units.find((unit) => unit.unit === trailing.value.unit);
739
+ if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? singularRollingCanonical(entry, false) : `de afgelopen ${trailing.value.amount} ${entry.plural}`);
740
+ }
741
+ const periods = [
742
+ ...staticPeriods,
743
+ ...currentYearDatePeriods(range, currentDateLabel),
744
+ ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range).map((phrase) => phrase.startsWith("Q") ? `K${phrase.slice(1)}` : phrase)], parsePeriod)
745
+ ];
746
+ return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `vanaf ${period}`, (period) => `voor ${period}`, (period) => `tot en met ${period}`, (period) => `na ${period}`, (lower, upper) => `van ${lower} tot en met ${upper}`, (period) => `vanaf ${period} tot nu toe`, (period) => `vanaf nu tot en met ${period}`, () => "tot nu toe", () => "vanaf nu");
747
+ };
748
+ const DutchContribution = new BaseLanguageContribution({
749
+ locale: "nl",
750
+ vocabulary: [
751
+ ...months,
752
+ ...monthAbbreviations.flatMap((aliases) => aliases),
753
+ ...units.flatMap((entry) => [
754
+ entry.singular,
755
+ entry.plural,
756
+ ...entry.current.split(" "),
757
+ ...entry.previous.split(" "),
758
+ ...entry.next.split(" ")
759
+ ]),
760
+ ...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
761
+ ...remainingPhrases.flatMap((entry) => entry.phrase.split(" ")),
762
+ "afgelopen",
763
+ "begin",
764
+ "binnen",
765
+ "eind",
766
+ "geleden",
767
+ "komende",
768
+ "laatste",
769
+ "na",
770
+ "over",
771
+ "rest",
772
+ "sinds",
773
+ "tussen",
774
+ "tot",
775
+ "vanaf",
776
+ "volgend",
777
+ "voor"
778
+ ],
779
+ normalize: normalizeNaturalText,
780
+ correct: correctWhitespaceSeparatedText,
781
+ parseExact: parseDutch,
782
+ suggest: suggestDutch,
783
+ render: renderDutch
784
+ });
785
+ const DutchLanguage = defineLanguagePlugin({
786
+ id: "chronolizer/language-nl",
787
+ effect: (context) => Effect.asVoid(context.register("chronolizer/language-nl", DutchContribution))
788
+ });
789
+ const DutchLanguageLayer = languagePluginsLayer([DutchLanguage]);
790
+ //#endregion
791
+ export { DutchContribution, DutchLanguage, DutchLanguageLayer };