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,901 @@
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, periodBoundaryCandidate, 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/fr.ts
10
+ const months = [
11
+ "janvier",
12
+ "février",
13
+ "mars",
14
+ "avril",
15
+ "mai",
16
+ "juin",
17
+ "juillet",
18
+ "août",
19
+ "septembre",
20
+ "octobre",
21
+ "novembre",
22
+ "décembre"
23
+ ];
24
+ const monthAbbreviations = [
25
+ ["jan", "janv"],
26
+ [
27
+ "fév",
28
+ "févr",
29
+ "fev",
30
+ "fevr",
31
+ "fevrier"
32
+ ],
33
+ ["mar"],
34
+ ["avr"],
35
+ [],
36
+ ["jun"],
37
+ ["juil", "jul"],
38
+ ["aout"],
39
+ ["sep", "sept"],
40
+ ["oct"],
41
+ ["nov"],
42
+ [
43
+ "déc",
44
+ "dec",
45
+ "decembre"
46
+ ]
47
+ ];
48
+ const units = [
49
+ {
50
+ unit: "day",
51
+ singular: "jour",
52
+ plural: "jours",
53
+ rollingSingular: "jour",
54
+ rollingPlural: "jours",
55
+ rollingGender: "masculine",
56
+ current: "aujourd'hui",
57
+ previous: "hier",
58
+ next: "demain",
59
+ toDate: "aujourd'hui jusqu'à maintenant",
60
+ remaining: "reste de la journée"
61
+ },
62
+ {
63
+ unit: "week",
64
+ singular: "semaine",
65
+ plural: "semaines",
66
+ rollingSingular: "semaine",
67
+ rollingPlural: "semaines",
68
+ rollingGender: "feminine",
69
+ current: "cette semaine",
70
+ previous: "la semaine dernière",
71
+ next: "la semaine prochaine",
72
+ toDate: "semaine à ce jour",
73
+ remaining: "reste de la semaine"
74
+ },
75
+ {
76
+ unit: "month",
77
+ singular: "mois",
78
+ plural: "mois",
79
+ rollingSingular: "mois",
80
+ rollingPlural: "mois",
81
+ rollingGender: "masculine",
82
+ current: "ce mois-ci",
83
+ previous: "le mois dernier",
84
+ next: "le mois prochain",
85
+ toDate: "mois à ce jour",
86
+ remaining: "reste du mois"
87
+ },
88
+ {
89
+ unit: "quarter",
90
+ singular: "trimestre",
91
+ plural: "trimestres",
92
+ rollingSingular: "trimestre",
93
+ rollingPlural: "trimestres",
94
+ rollingGender: "masculine",
95
+ current: "ce trimestre",
96
+ previous: "le trimestre dernier",
97
+ next: "le trimestre prochain",
98
+ toDate: "trimestre à ce jour",
99
+ remaining: "reste du trimestre"
100
+ },
101
+ {
102
+ unit: "year",
103
+ singular: "an",
104
+ plural: "ans",
105
+ rollingSingular: "année",
106
+ rollingPlural: "années",
107
+ rollingGender: "feminine",
108
+ current: "cette année",
109
+ previous: "l'année dernière",
110
+ next: "l'année prochaine",
111
+ toDate: "année à ce jour",
112
+ remaining: "reste de l'année"
113
+ }
114
+ ];
115
+ const rollingArticle = (entry) => entry.rollingGender === "feminine" ? "la" : "le";
116
+ const indefiniteArticle = (entry) => entry.unit === "week" ? "une" : "un";
117
+ const rollingModifier = (entry, future, plural) => {
118
+ if (future) {
119
+ if (entry.rollingGender === "feminine") return plural ? "prochaines" : "prochaine";
120
+ return plural ? "prochains" : "prochain";
121
+ }
122
+ if (entry.rollingGender === "feminine") return plural ? "dernières" : "dernière";
123
+ return plural ? "derniers" : "dernier";
124
+ };
125
+ const withDe = (period) => {
126
+ if (period.startsWith("le ")) return `du ${period.slice(3)}`;
127
+ if (period.startsWith("les ")) return `des ${period.slice(4)}`;
128
+ if (period.startsWith("la ")) return `de la ${period.slice(3)}`;
129
+ if (period.startsWith("l'")) return `de ${period}`;
130
+ return `de ${period}`;
131
+ };
132
+ const withA = (period) => {
133
+ if (period.startsWith("le ")) return `au ${period.slice(3)}`;
134
+ if (period.startsWith("les ")) return `aux ${period.slice(4)}`;
135
+ if (period.startsWith("la ")) return `à la ${period.slice(3)}`;
136
+ if (period.startsWith("l'")) return `à ${period}`;
137
+ return `à ${period}`;
138
+ };
139
+ const afterDe = (period) => {
140
+ if (period.startsWith("du ")) return `le ${period.slice(3)}`;
141
+ if (period.startsWith("de la ")) return `la ${period.slice(6)}`;
142
+ if (period.startsWith("de l'")) return `l'${period.slice(5)}`;
143
+ return period.startsWith("de ") ? period.slice(3) : period;
144
+ };
145
+ const startsWithDay = (period) => period[0] !== void 0 && period[0] >= "0" && period[0] <= "9";
146
+ const rangeLabel = (lower, upper) => {
147
+ return `${startsWithDay(lower) ? `du ${lower}` : withDe(lower)} ${startsWithDay(upper) ? `au ${upper}` : withA(upper)}`;
148
+ };
149
+ const untilLabel = (period) => `jusqu'${withA(period)}`;
150
+ const unitAliases = [
151
+ ["jour", "day"],
152
+ ["jours", "day"],
153
+ ["semaine", "week"],
154
+ ["semaines", "week"],
155
+ ["mois", "month"],
156
+ ["trimestre", "quarter"],
157
+ ["trimestres", "quarter"],
158
+ ["an", "year"],
159
+ ["ans", "year"],
160
+ ["année", "year"],
161
+ ["annee", "year"],
162
+ ["années", "year"],
163
+ ["annees", "year"]
164
+ ];
165
+ const periodAliases = [
166
+ ...units.flatMap((entry) => [
167
+ [
168
+ entry.current,
169
+ entry.unit,
170
+ 0,
171
+ entry.current
172
+ ],
173
+ [
174
+ entry.previous,
175
+ entry.unit,
176
+ -1,
177
+ entry.previous
178
+ ],
179
+ [
180
+ entry.next,
181
+ entry.unit,
182
+ 1,
183
+ entry.next
184
+ ]
185
+ ]),
186
+ [
187
+ "semaine en cours",
188
+ "week",
189
+ 0,
190
+ "cette semaine"
191
+ ],
192
+ [
193
+ "semaine dernière",
194
+ "week",
195
+ -1,
196
+ "la semaine dernière"
197
+ ],
198
+ [
199
+ "semaine derniere",
200
+ "week",
201
+ -1,
202
+ "la semaine dernière"
203
+ ],
204
+ [
205
+ "semaine suivante",
206
+ "week",
207
+ 1,
208
+ "la semaine prochaine"
209
+ ],
210
+ [
211
+ "le mois en cours",
212
+ "month",
213
+ 0,
214
+ "ce mois-ci"
215
+ ],
216
+ [
217
+ "ce mois",
218
+ "month",
219
+ 0,
220
+ "ce mois-ci"
221
+ ],
222
+ [
223
+ "mois dernier",
224
+ "month",
225
+ -1,
226
+ "le mois dernier"
227
+ ],
228
+ [
229
+ "mois prochain",
230
+ "month",
231
+ 1,
232
+ "le mois prochain"
233
+ ],
234
+ [
235
+ "mois à venir",
236
+ "month",
237
+ 1,
238
+ "le mois prochain"
239
+ ],
240
+ [
241
+ "l'année en cours",
242
+ "year",
243
+ 0,
244
+ "cette année"
245
+ ],
246
+ [
247
+ "cette annee",
248
+ "year",
249
+ 0,
250
+ "cette année"
251
+ ],
252
+ [
253
+ "année dernière",
254
+ "year",
255
+ -1,
256
+ "l'année dernière"
257
+ ],
258
+ [
259
+ "annee derniere",
260
+ "year",
261
+ -1,
262
+ "l'année dernière"
263
+ ],
264
+ [
265
+ "année passée",
266
+ "year",
267
+ -1,
268
+ "l'année dernière"
269
+ ],
270
+ [
271
+ "année prochaine",
272
+ "year",
273
+ 1,
274
+ "l'année prochaine"
275
+ ],
276
+ [
277
+ "annee prochaine",
278
+ "year",
279
+ 1,
280
+ "l'année prochaine"
281
+ ],
282
+ [
283
+ "trimestre dernier",
284
+ "quarter",
285
+ -1,
286
+ "le trimestre dernier"
287
+ ],
288
+ [
289
+ "trimestre prochain",
290
+ "quarter",
291
+ 1,
292
+ "le trimestre prochain"
293
+ ],
294
+ [
295
+ "avant-hier",
296
+ "day",
297
+ -2,
298
+ "avant-hier"
299
+ ],
300
+ [
301
+ "après-demain",
302
+ "day",
303
+ 2,
304
+ "après-demain"
305
+ ],
306
+ [
307
+ "apres-demain",
308
+ "day",
309
+ 2,
310
+ "après-demain"
311
+ ],
312
+ [
313
+ "l'avant-dernière semaine",
314
+ "week",
315
+ -2,
316
+ "l'avant-dernière semaine"
317
+ ],
318
+ [
319
+ "la semaine après la prochaine",
320
+ "week",
321
+ 2,
322
+ "la semaine après la prochaine"
323
+ ],
324
+ [
325
+ "le mois avant le dernier",
326
+ "month",
327
+ -2,
328
+ "le mois avant le dernier"
329
+ ],
330
+ [
331
+ "le mois après le prochain",
332
+ "month",
333
+ 2,
334
+ "le mois après le prochain"
335
+ ],
336
+ [
337
+ "le trimestre avant le dernier",
338
+ "quarter",
339
+ -2,
340
+ "le trimestre avant le dernier"
341
+ ],
342
+ [
343
+ "le trimestre après le prochain",
344
+ "quarter",
345
+ 2,
346
+ "le trimestre après le prochain"
347
+ ],
348
+ [
349
+ "l'année avant la dernière",
350
+ "year",
351
+ -2,
352
+ "l'année avant la dernière"
353
+ ],
354
+ [
355
+ "l'année après la prochaine",
356
+ "year",
357
+ 2,
358
+ "l'année après la prochaine"
359
+ ]
360
+ ];
361
+ const periodArticle = {
362
+ day: "de la journée",
363
+ week: "de la semaine",
364
+ month: "du mois",
365
+ quarter: "du trimestre",
366
+ year: "de l'année"
367
+ };
368
+ const toDatePhrases = units.flatMap((entry) => {
369
+ const yearAliases = entry.unit === "year" ? ["depuis le début d'année", "depuis le début de l'année en cours"] : [];
370
+ return [
371
+ entry.toDate,
372
+ `depuis le début ${periodArticle[entry.unit]}`,
373
+ `depuis le commencement ${periodArticle[entry.unit]}`,
374
+ `${entry.current} jusqu'à maintenant`,
375
+ ...yearAliases
376
+ ].map((phrase) => ({
377
+ entry,
378
+ phrase
379
+ }));
380
+ });
381
+ const remainingAliases = {
382
+ day: ["reste d'aujourd'hui"],
383
+ week: ["reste de cette semaine", "reste de la semaine en cours"],
384
+ month: ["reste de ce mois", "reste de ce mois-ci"],
385
+ quarter: ["reste de ce trimestre"],
386
+ year: ["reste de cette année", "reste de l'année en cours"]
387
+ };
388
+ const remainingPhrases = units.flatMap((entry) => [
389
+ entry.remaining,
390
+ `ce qui reste ${periodArticle[entry.unit]}`,
391
+ ...remainingAliases[entry.unit]
392
+ ].map((phrase) => ({
393
+ entry,
394
+ phrase
395
+ })));
396
+ const relativeYearDirection = (value) => {
397
+ if (value.includes("derni") || value.includes("pass")) return -1;
398
+ if (value.includes("prochain")) return 1;
399
+ return 0;
400
+ };
401
+ const relativeYearName = (direction) => {
402
+ if (direction < 0) return "l'année dernière";
403
+ if (direction > 0) return "l'année prochaine";
404
+ return "cette année";
405
+ };
406
+ const monthNumber = (value) => {
407
+ const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
408
+ const full = months.findIndex((month) => month === normalized);
409
+ if (full !== -1) return full + 1;
410
+ const short = monthAbbreviations.findIndex((aliases) => aliases.some((alias) => alias === normalized));
411
+ return short === -1 ? void 0 : short + 1;
412
+ };
413
+ const dateLabel = (day, month, year) => `${day === 1 ? "1er" : day} ${textAt(months, month - 1)} ${year}`;
414
+ const currentDateLabel = (day, month) => `${day === 1 ? "1er" : day} ${textAt(months, month - 1)}`;
415
+ const parseNamedDate = (input) => {
416
+ const named = String$1.match(/^(?:le )?(1er|[0-3]?\d)(?: de)? ([a-zàâçéèêëîïôûùüÿœ]+\.?),?(?: de)? (\d{4})$/u)(input);
417
+ if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1).replace(/er$/u, ""), monthNumber, dateLabel);
418
+ const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
419
+ if (Option.isSome(numeric)) {
420
+ const year = validYear(textAt(numeric.value, 3));
421
+ const month = Number(textAt(numeric.value, 2));
422
+ const day = Number(textAt(numeric.value, 1));
423
+ if (year !== void 0 && month >= 1 && month <= 12) {
424
+ const value = isoDate(year, month, day);
425
+ if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
426
+ }
427
+ }
428
+ const current = String$1.match(/^(?:le )?(1er|[0-3]?\d)(?: de)? ([a-zàâçéèêëîïôûùüÿœ]+\.?)$/u)(input);
429
+ if (Option.isSome(current)) return namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1).replace(/er$/u, ""), monthNumber, currentDateLabel);
430
+ const relative = String$1.match(/^(?:le )?(1er|[0-3]?\d) (de .+|du .+)$/u)(input);
431
+ if (Option.isNone(relative)) return Option.none();
432
+ const periodText = afterDe(textAt(relative.value, 2));
433
+ const alias = periodAliases.find((entry) => entry[0] === periodText && entry[1] === "month");
434
+ if (alias === void 0) return Option.none();
435
+ const day = Number(textAt(relative.value, 1).replace(/er$/u, ""));
436
+ const month = relativePeriod(alias[1], alias[2], alias[3]);
437
+ return periodDay(month, day, `${day === 1 ? "1er" : day} ${withDe(alias[3])}`);
438
+ };
439
+ const quarterNumber = (value) => {
440
+ if ((value.startsWith("q") || value.startsWith("t")) && value.length === 2) return Number(value.slice(1));
441
+ if (value === "1er" || value === "premier") return 1;
442
+ if (value === "2e" || value === "2ème" || value === "deuxième" || value === "deuxieme") return 2;
443
+ if (value === "3e" || value === "3ème" || value === "troisième" || value === "troisieme") return 3;
444
+ return value === "4e" || value === "4ème" || value === "quatrième" || value === "quatrieme" ? 4 : void 0;
445
+ };
446
+ const parseQuarter = (input) => {
447
+ const fixed = String$1.match(/^(t[1-4]|q[1-4]|1er|premier|2e|2ème|deuxième|deuxieme|3e|3ème|troisième|troisieme|4e|4ème|quatrième|quatrieme)(?: trimestre)?(?: de)? (\d{4})$/u)(input);
448
+ if (Option.isSome(fixed)) {
449
+ const quarter = quarterNumber(textAt(fixed.value, 1));
450
+ const year = validYear(textAt(fixed.value, 2));
451
+ if (quarter !== void 0 && year !== void 0) return Option.some(fixedQuarterPeriod(year, quarter, `T${quarter} ${year}`));
452
+ }
453
+ const relative = String$1.match(/^(t[1-4]|q[1-4]) de (l'année dernière|l'annee derniere|l'année prochaine|l'annee prochaine|cette année|cette annee)$/u)(input);
454
+ if (Option.isSome(relative)) {
455
+ const quarter = quarterNumber(textAt(relative.value, 1));
456
+ const direction = relativeYearDirection(textAt(relative.value, 2));
457
+ if (quarter !== void 0) return Option.some(quarterOfRelativeYear(quarter, direction, `T${quarter} de ${relativeYearName(direction)}`));
458
+ }
459
+ const standalone = String$1.match(/^(?:le )?(t[1-4]|q[1-4])$/u)(input);
460
+ if (Option.isNone(standalone)) return Option.none();
461
+ const quarter = quarterNumber(textAt(standalone.value, 1));
462
+ return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `T${quarter}`));
463
+ };
464
+ const parseBasePeriod = (input) => {
465
+ const absoluteDate = absoluteDatePeriod(input, "fr");
466
+ if (Option.isSome(absoluteDate)) return absoluteDate;
467
+ const namedDate = parseNamedDate(input);
468
+ if (Option.isSome(namedDate)) return namedDate;
469
+ const quarter = parseQuarter(input);
470
+ if (Option.isSome(quarter)) return quarter;
471
+ const yearMatch = String$1.match(/^(?:(?:l')?année |annee )?(\d{4})$/u)(input);
472
+ if (Option.isSome(yearMatch)) {
473
+ const year = validYear(textAt(yearMatch.value, 1));
474
+ if (year !== void 0) return Option.some(fixedYearPeriod(year, String(year)));
475
+ }
476
+ const monthYear = String$1.match(/^([a-zàâçéèêëîïôûùüÿœ]+\.?)(?: de)? (\d{4})$/u)(input);
477
+ if (Option.isSome(monthYear)) {
478
+ const month = monthNumber(textAt(monthYear.value, 1));
479
+ const year = validYear(textAt(monthYear.value, 2));
480
+ if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
481
+ }
482
+ const relativeMonth = String$1.match(/^([a-zàâçéèêëîïôûùüÿœ]+\.?) de (l'année dernière|l'annee derniere|l'année prochaine|l'annee prochaine|cette année|cette annee)$/u)(input);
483
+ if (Option.isSome(relativeMonth)) {
484
+ const month = monthNumber(textAt(relativeMonth.value, 1));
485
+ const yearText = textAt(relativeMonth.value, 2);
486
+ const direction = relativeYearDirection(yearText);
487
+ if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${textAt(months, month - 1)} de ${relativeYearName(direction)}`));
488
+ }
489
+ const standaloneMonth = monthNumber(input);
490
+ if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, textAt(months, standaloneMonth - 1)));
491
+ const alias = periodAliases.find((entry) => entry[0] === input);
492
+ if (alias !== void 0) return Option.some(relativePeriod(alias[1], alias[2], alias[3]));
493
+ if ([
494
+ "week-end",
495
+ "le week-end",
496
+ "ce week-end",
497
+ "weekend",
498
+ "ce weekend"
499
+ ].includes(input)) return Option.some(relativeWeekend(0, "ce week-end"));
500
+ if ([
501
+ "le week-end dernier",
502
+ "week-end dernier",
503
+ "le weekend dernier"
504
+ ].includes(input)) return Option.some(relativeWeekend(-1, "le week-end dernier"));
505
+ if ([
506
+ "le week-end prochain",
507
+ "week-end prochain",
508
+ "le weekend prochain"
509
+ ].includes(input)) return Option.some(relativeWeekend(1, "le week-end prochain"));
510
+ if (input === "l'avant-dernier week-end") return Option.some(relativeWeekend(-2, input));
511
+ if (input === "le week-end après le prochain") return Option.some(relativeWeekend(2, input));
512
+ return Option.none();
513
+ };
514
+ const parsePeriod = (input) => {
515
+ const edge = String$1.match(/^(?:le |la |l')?(début|debut|commencement|fin)(?: du | de la | de l'| de )(.+)$/u)(input);
516
+ if (Option.isSome(edge)) {
517
+ const edgeName = textAt(edge.value, 1);
518
+ const period = parseBasePeriod(textAt(edge.value, 2));
519
+ if (Option.isSome(period)) {
520
+ const isEnd = edgeName === "fin";
521
+ const canonical = `${isEnd ? "fin" : "début"} ${withDe(period.value.canonical)}`;
522
+ return Option.some(isEnd ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
523
+ }
524
+ }
525
+ const wrapper = [
526
+ "pendant ",
527
+ "en ",
528
+ "tout ",
529
+ "tout le ",
530
+ "toute la "
531
+ ].find((prefix) => input.startsWith(prefix));
532
+ return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
533
+ };
534
+ const countedUnit = (value, amount) => {
535
+ const plural = value === "mois" || value.endsWith("s");
536
+ if (value !== "mois" && plural === (amount === 1)) return void 0;
537
+ const unit = unitAliases.find((entry) => entry[0] === value)?.[1];
538
+ return unit === void 0 ? void 0 : units.find((entry) => entry.unit === unit);
539
+ };
540
+ const modifierAgreesWithNoun = (input, noun) => {
541
+ const match = String$1.match(/(?:^| )(derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes|prochains|prochaines|suivants|suivantes)(?: |$)/u)(input);
542
+ if (Option.isNone(match)) return true;
543
+ const modifier = textAt(match.value, 1);
544
+ return (noun.startsWith("semaine") || noun.startsWith("année") || noun.startsWith("annee")) === [
545
+ "dernières",
546
+ "dernieres",
547
+ "passées",
548
+ "passes",
549
+ "précédentes",
550
+ "precedentes",
551
+ "prochaines",
552
+ "suivantes"
553
+ ].includes(modifier);
554
+ };
555
+ const countedUnitPattern = "jour|jours|semaine|semaines|mois|trimestre|trimestres|an|ans|année|annee|années|annees";
556
+ const compileCountedPattern = (source) => new RegExp(source.replace("UNIT", countedUnitPattern), "u");
557
+ const calendarPastPattern = compileCountedPattern("^il y a ([1-9]\\d*) (UNIT)$");
558
+ const calendarFuturePattern = compileCountedPattern("^dans ([1-9]\\d*) (UNIT)$");
559
+ const rollingSincePattern = compileCountedPattern("^depuis ([1-9]\\d*) (UNIT)$");
560
+ const rollingBarePattern = compileCountedPattern("^([1-9]\\d*) (UNIT)$");
561
+ const rollingPastPatterns = [
562
+ compileCountedPattern("^(?:(?:les|la) )?(?:derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes) ([1-9]\\d*) (UNIT)$"),
563
+ compileCountedPattern("^(?:(?:les|la|au cours des|pendant les) )?([1-9]\\d*) (?:derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes) (UNIT)$"),
564
+ compileCountedPattern("^([1-9]\\d*) (UNIT) (?:derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes)$")
565
+ ];
566
+ const rollingFuturePatterns = [
567
+ compileCountedPattern("^(?:(?:les|la) )?(?:prochains|prochaines|suivants|suivantes) ([1-9]\\d*) (UNIT)$"),
568
+ compileCountedPattern("^(?:(?:les|la|au cours des|pendant les) )?([1-9]\\d*) (?:prochains|prochaines|suivants|suivantes) (UNIT)$"),
569
+ compileCountedPattern("^([1-9]\\d*) (UNIT) (?:prochains|prochaines|suivants|suivantes|à venir)$")
570
+ ];
571
+ const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
572
+ const relativeCountPhrase = (amount, entry, future) => {
573
+ if (amount === 1) return singularRollingCanonical(entry, future);
574
+ return `les ${amount} ${rollingModifier(entry, future, true)} ${entry.rollingPlural}`;
575
+ };
576
+ const singularRollingCanonical = (entry, future) => {
577
+ const article = indefiniteArticle(entry);
578
+ return future ? `à partir de maintenant pendant ${article} ${entry.singular}` : `depuis ${article} ${entry.singular}`;
579
+ };
580
+ const singularRollingPhrases = units.flatMap((entry) => {
581
+ const period = `${rollingArticle(entry)} ${rollingModifier(entry, false, false)} ${entry.rollingSingular}`;
582
+ const futurePeriod = `${rollingArticle(entry)} ${rollingModifier(entry, true, false)} ${entry.rollingSingular}`;
583
+ const article = indefiniteArticle(entry);
584
+ return [
585
+ {
586
+ phrase: period,
587
+ entry,
588
+ future: false
589
+ },
590
+ {
591
+ phrase: `au cours ${withDe(period)}`,
592
+ entry,
593
+ future: false
594
+ },
595
+ {
596
+ phrase: `depuis ${article} ${entry.singular}`,
597
+ entry,
598
+ future: false
599
+ },
600
+ {
601
+ phrase: `pendant ${futurePeriod}`,
602
+ entry,
603
+ future: true
604
+ },
605
+ {
606
+ phrase: `à partir de maintenant pendant ${article} ${entry.singular}`,
607
+ entry,
608
+ future: true
609
+ }
610
+ ];
611
+ });
612
+ const singularCalendarOffsets = units.flatMap((entry) => {
613
+ const quantity = `${indefiniteArticle(entry)} ${entry.singular}`;
614
+ return [{
615
+ phrase: `il y a ${quantity}`,
616
+ entry,
617
+ direction: -1
618
+ }, {
619
+ phrase: `dans ${quantity}`,
620
+ entry,
621
+ direction: 1
622
+ }];
623
+ });
624
+ const parseCalendarOffset = (input) => {
625
+ const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
626
+ if (singular !== void 0) return Option.some(candidate(periodRange(relativePeriod(singular.entry.unit, singular.direction, singular.phrase)), singular.phrase));
627
+ const past = String$1.match(calendarPastPattern)(input);
628
+ const future = String$1.match(calendarFuturePattern)(input);
629
+ const match = Option.firstSomeOf([past, future]);
630
+ if (Option.isNone(match)) return Option.none();
631
+ const amount = parseTrailingCount(textAt(match.value, 1));
632
+ if (Option.isNone(amount)) return Option.none();
633
+ const entry = countedUnit(textAt(match.value, 2), amount.value);
634
+ if (entry === void 0) return Option.none();
635
+ const direction = Option.isSome(past) ? -amount.value : amount.value;
636
+ const noun = amount.value === 1 ? entry.singular : entry.plural;
637
+ const canonical = direction < 0 ? `il y a ${amount.value} ${noun}` : `dans ${amount.value} ${noun}`;
638
+ return Option.some(candidate(periodRange(relativePeriod(entry.unit, direction, canonical)), canonical));
639
+ };
640
+ const parseRollingPeriod = (input) => {
641
+ const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
642
+ if (singular !== void 0) {
643
+ const range = singular.future ? futureRange(1, singular.entry.unit) : trailingRange(1, singular.entry.unit);
644
+ return Option.some(candidate(range, singularRollingCanonical(singular.entry, singular.future)));
645
+ }
646
+ const since = String$1.match(rollingSincePattern)(input);
647
+ const past = firstPatternMatch(input, rollingPastPatterns);
648
+ const bare = String$1.match(rollingBarePattern)(input);
649
+ const future = firstPatternMatch(input, rollingFuturePatterns);
650
+ const match = Option.firstSomeOf([
651
+ since,
652
+ past,
653
+ bare,
654
+ future
655
+ ]);
656
+ if (Option.isNone(match)) return Option.none();
657
+ const amount = parseTrailingCount(textAt(match.value, 1));
658
+ if (Option.isNone(amount)) return Option.none();
659
+ const noun = textAt(match.value, 2);
660
+ const entry = countedUnit(noun, amount.value);
661
+ if (entry === void 0 || !modifierAgreesWithNoun(input, noun)) return Option.none();
662
+ const isFuture = Option.isSome(future);
663
+ const range = isFuture ? futureRange(amount.value, entry.unit) : trailingRange(amount.value, entry.unit);
664
+ return Option.some(candidate(range, relativeCountPhrase(amount.value, entry, isFuture)));
665
+ };
666
+ const parseElidedDateRange = (input) => {
667
+ const joined = String$1.match(/^(?:du (1er|[0-3]?\d) au|entre le (1er|[0-3]?\d) et le) (1er|[0-3]?\d) (.+)$/u)(input);
668
+ const dashed = String$1.match(/^(1er|[0-3]?\d)[–—-](1er|[0-3]?\d) (.+)$/u)(input);
669
+ const match = Option.firstSomeOf([joined, dashed]);
670
+ if (Option.isNone(match)) return Option.none();
671
+ const isJoined = Option.isSome(joined);
672
+ const lowerDay = textAt(match.value, 1) || textAt(match.value, 2);
673
+ const upperDay = textAt(match.value, isJoined ? 3 : 2);
674
+ const period = afterDe(textAt(match.value, isJoined ? 4 : 3));
675
+ const datePeriod = periodAliases.some((entry) => entry[0] === period && entry[1] === "month") ? withDe(period) : period;
676
+ return joinedPeriodCandidate(`du ${lowerDay} ${datePeriod} au ${upperDay} ${datePeriod}`, [["du ", " au "]], parsePeriod, rangeLabel);
677
+ };
678
+ const boundaryCandidate = (input) => {
679
+ const included = String$1.match(/^jusqu'à (.+) (?:inclus|incluse|inclusivement)$/u)(input);
680
+ if (Option.isSome(included)) {
681
+ const period = parsePeriod(textAt(included.value, 1));
682
+ if (Option.isSome(period)) return Option.some(periodBoundaryCandidate(period.value, "through", untilLabel(period.value.canonical)));
683
+ }
684
+ return openBoundaryCandidate(input, [
685
+ ["jusqu'avant ", "before"],
686
+ ["jusqu'au début de ", "before"],
687
+ ["jusqu'à et y compris ", "through"],
688
+ ["à partir de ", "since"],
689
+ ["depuis ", "since"],
690
+ ["avant ", "before"],
691
+ ["jusqu'à ", "through"],
692
+ ["jusqu'au ", "through"],
693
+ ["jusqu'en ", "through"],
694
+ ["après ", "after"],
695
+ ["apres ", "after"]
696
+ ], parsePeriod);
697
+ };
698
+ const parseFrench = (input) => {
699
+ const remaining = remainingPhrases.find((entry) => entry.phrase === input);
700
+ if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry.unit), remaining.entry.remaining));
701
+ if ([
702
+ "jusqu'à aujourd'hui",
703
+ "jusqu'à maintenant",
704
+ "à ce jour"
705
+ ].includes(input)) return Option.some(candidate(untilNowRange(), "jusqu'à maintenant"));
706
+ if ([
707
+ "depuis maintenant",
708
+ "à partir de maintenant",
709
+ "désormais"
710
+ ].includes(input)) return Option.some(candidate(fromNowRange(), "depuis maintenant"));
711
+ const offset = parseCalendarOffset(input);
712
+ if (Option.isSome(offset)) return offset;
713
+ const rolling = parseRollingPeriod(input);
714
+ if (Option.isSome(rolling)) return rolling;
715
+ const toDate = toDatePhrases.find((entry) => entry.phrase === input);
716
+ if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
717
+ const elided = parseElidedDateRange(input);
718
+ if (Option.isSome(elided)) return elided;
719
+ const nowBounded = joinedNowCandidate(input, [
720
+ ["depuis ", " jusqu'à maintenant"],
721
+ ["depuis ", " jusqu'à aujourd'hui"],
722
+ ["entre ", " et aujourd'hui"]
723
+ ], [
724
+ "d'aujourd'hui à ",
725
+ "de maintenant à ",
726
+ "entre aujourd'hui et "
727
+ ], parsePeriod, (period) => `depuis ${period} jusqu'à maintenant`, (period) => `de maintenant à ${period}`);
728
+ if (Option.isSome(nowBounded)) return nowBounded;
729
+ const bounded = joinedPeriodCandidate(input, [
730
+ ["depuis ", " jusqu'à "],
731
+ ["du ", " au "],
732
+ ["de ", " à "],
733
+ ["entre ", " et "],
734
+ ["", " - "],
735
+ ["", " – "],
736
+ ["", " — "],
737
+ ["", " jusqu'à "],
738
+ ["", " au "]
739
+ ], parsePeriod, rangeLabel);
740
+ if (Option.isSome(bounded)) return bounded;
741
+ const boundary = boundaryCandidate(input);
742
+ if (Option.isSome(boundary)) return boundary;
743
+ return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
744
+ };
745
+ const weekendPhrases = [
746
+ "ce week-end",
747
+ "le week-end dernier",
748
+ "le week-end prochain",
749
+ "l'avant-dernier week-end",
750
+ "le week-end après le prochain"
751
+ ];
752
+ const edgePeriodPhrases = [
753
+ ...units.filter((entry) => entry.unit !== "day").flatMap((entry) => [
754
+ entry.current,
755
+ entry.previous,
756
+ entry.next
757
+ ]),
758
+ ...weekendPhrases,
759
+ ...months
760
+ ].flatMap((period) => [`début ${withDe(period)}`, `fin ${withDe(period)}`]);
761
+ const staticPeriodPhrases = [
762
+ ...periodAliases.map((entry) => entry[0]),
763
+ ...weekendPhrases,
764
+ ...edgePeriodPhrases,
765
+ ...[
766
+ 1,
767
+ 2,
768
+ 3,
769
+ 4
770
+ ].flatMap((quarter) => [
771
+ `t${quarter}`,
772
+ `t${quarter} de cette année`,
773
+ `t${quarter} de l'année dernière`,
774
+ `t${quarter} de l'année prochaine`
775
+ ]),
776
+ ...months.flatMap((month) => [
777
+ month,
778
+ `${month} de l'année dernière`,
779
+ `${month} de l'année prochaine`
780
+ ])
781
+ ];
782
+ const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
783
+ const boundaryPrefixes = [
784
+ "depuis ",
785
+ "avant ",
786
+ "jusqu'à ",
787
+ "après "
788
+ ];
789
+ const countedSuggestions = (input) => {
790
+ const amount = naturalCount(input);
791
+ if (amount === void 0) return [];
792
+ return units.flatMap((entry) => {
793
+ const noun = amount === 1 ? entry.singular : entry.plural;
794
+ const rollingNoun = amount === 1 ? entry.rollingSingular : entry.rollingPlural;
795
+ const past = rollingModifier(entry, false, amount !== 1);
796
+ const future = rollingModifier(entry, true, amount !== 1);
797
+ return [
798
+ relativeCountPhrase(amount, entry, false),
799
+ `${amount} ${past} ${rollingNoun}`,
800
+ `${amount} ${noun}`,
801
+ relativeCountPhrase(amount, entry, true),
802
+ `${amount} ${future} ${rollingNoun}`,
803
+ `${amount} ${rollingNoun} à venir`,
804
+ `il y a ${amount} ${noun}`,
805
+ `dans ${amount} ${noun}`
806
+ ];
807
+ });
808
+ };
809
+ const frenchSuggestionPhrases = [
810
+ ...units.map((entry) => entry.toDate),
811
+ ...units.map((entry) => entry.remaining),
812
+ ...singularRollingPhrases.map((entry) => entry.phrase),
813
+ ...singularCalendarOffsets.map((entry) => entry.phrase),
814
+ ...staticPeriodPhrases,
815
+ ...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
816
+ "jusqu'à maintenant",
817
+ "depuis maintenant"
818
+ ];
819
+ const suggestFrench = (input, limit) => {
820
+ const fixed = fixedCalendarPeriodPhrases(input, months);
821
+ return completeNaturalPhrases(input, [
822
+ ...frenchSuggestionPhrases,
823
+ ...fixed,
824
+ ...prefixNaturalPhrases(fixed, boundaryPrefixes),
825
+ ...countedSuggestions(input)
826
+ ], limit);
827
+ };
828
+ const renderFrench = (range) => {
829
+ const offset = calendarPeriodOffset(range);
830
+ if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
831
+ const entry = units.find((unit) => unit.unit === offset.value.unit);
832
+ if (entry !== void 0) {
833
+ const amount = Math.abs(offset.value.amount);
834
+ const noun = amount === 1 ? entry.singular : entry.plural;
835
+ return Option.some(offset.value.amount < 0 ? `il y a ${amount} ${noun}` : `dans ${amount} ${noun}`);
836
+ }
837
+ }
838
+ const future = futurePeriod(range);
839
+ if (Option.isSome(future)) {
840
+ const entry = units.find((unit) => unit.unit === future.value.unit);
841
+ if (entry !== void 0) return Option.some(future.value.amount === 1 ? singularRollingCanonical(entry, true) : relativeCountPhrase(future.value.amount, entry, true));
842
+ }
843
+ const trailing = trailingPeriod(range);
844
+ if (Option.isSome(trailing)) {
845
+ const entry = units.find((unit) => unit.unit === trailing.value.unit);
846
+ if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? singularRollingCanonical(entry, false) : relativeCountPhrase(trailing.value.amount, entry, false));
847
+ }
848
+ const periods = [
849
+ ...staticPeriods,
850
+ ...currentYearDatePeriods(range, currentDateLabel),
851
+ ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)
852
+ ];
853
+ return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `depuis ${period}`, (period) => `avant ${period}`, untilLabel, (period) => `après ${period}`, rangeLabel, (period) => `depuis ${period} jusqu'à maintenant`, (period) => `de maintenant à ${period}`, () => "jusqu'à maintenant", () => "depuis maintenant");
854
+ };
855
+ const normalizeFrench = (input, locale) => normalizeNaturalText(input, locale).replaceAll("’", "'");
856
+ const FrenchContribution = new BaseLanguageContribution({
857
+ locale: "fr",
858
+ vocabulary: [
859
+ ...months,
860
+ ...monthAbbreviations.flatMap((aliases) => aliases),
861
+ ...units.flatMap((entry) => [
862
+ entry.singular,
863
+ entry.plural,
864
+ ...entry.current.split(" "),
865
+ ...entry.previous.split(" "),
866
+ ...entry.next.split(" ")
867
+ ]),
868
+ ...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
869
+ ...remainingPhrases.flatMap((entry) => entry.phrase.split(" ")),
870
+ "année",
871
+ "annee",
872
+ "après",
873
+ "avant",
874
+ "commencement",
875
+ "dans",
876
+ "depuis",
877
+ "dernier",
878
+ "début",
879
+ "entre",
880
+ "fin",
881
+ "inclus",
882
+ "maintenant",
883
+ "passé",
884
+ "précédent",
885
+ "prochain",
886
+ "reste",
887
+ "jusqu'à"
888
+ ],
889
+ normalize: normalizeFrench,
890
+ correct: correctWhitespaceSeparatedText,
891
+ parseExact: parseFrench,
892
+ suggest: suggestFrench,
893
+ render: renderFrench
894
+ });
895
+ const FrenchLanguage = defineLanguagePlugin({
896
+ id: "chronolizer/language-fr",
897
+ effect: (context) => Effect.asVoid(context.register("chronolizer/language-fr", FrenchContribution))
898
+ });
899
+ const FrenchLanguageLayer = languagePluginsLayer([FrenchLanguage]);
900
+ //#endregion
901
+ export { FrenchContribution, FrenchLanguage, FrenchLanguageLayer };