chronolizer 0.1.0 → 0.2.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 +70 -7
  2. package/dist/ast/constructors.d.mts +140 -0
  3. package/dist/ast/constructors.mjs +25 -0
  4. package/dist/ast/fold.d.mts +12 -0
  5. package/dist/ast/fold.mjs +51 -0
  6. package/dist/ast/normalize.d.mts +42 -0
  7. package/dist/ast/normalize.mjs +32 -0
  8. package/dist/ast/schemas.d.mts +87 -0
  9. package/dist/ast/schemas.mjs +74 -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 +93 -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 +53 -0
  26. package/dist/language/registry.d.mts +17 -0
  27. package/dist/language/registry.mjs +134 -0
  28. package/dist/locales/cs.d.mts +10 -0
  29. package/dist/locales/cs.mjs +693 -0
  30. package/dist/locales/de.d.mts +10 -0
  31. package/dist/locales/de.mjs +828 -0
  32. package/dist/locales/en.d.mts +10 -0
  33. package/dist/locales/en.mjs +550 -0
  34. package/dist/locales/es.d.mts +10 -0
  35. package/dist/locales/es.mjs +660 -0
  36. package/dist/locales/fr.d.mts +10 -0
  37. package/dist/locales/fr.mjs +737 -0
  38. package/dist/locales/nl.d.mts +10 -0
  39. package/dist/locales/nl.mjs +652 -0
  40. package/dist/locales/pl.d.mts +10 -0
  41. package/dist/locales/pl.mjs +711 -0
  42. package/dist/locales/shared.mjs +289 -0
  43. package/dist/locales/tr.d.mts +10 -0
  44. package/dist/locales/tr.mjs +660 -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 +66 -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,737 @@
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 { calendarPeriodOffset, candidate, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, 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
+ current: "aujourd'hui",
54
+ previous: "hier",
55
+ next: "demain",
56
+ toDate: "aujourd'hui jusqu'à maintenant",
57
+ remaining: "reste de la journée"
58
+ },
59
+ {
60
+ unit: "week",
61
+ singular: "semaine",
62
+ plural: "semaines",
63
+ current: "cette semaine",
64
+ previous: "la semaine dernière",
65
+ next: "la semaine prochaine",
66
+ toDate: "semaine à ce jour",
67
+ remaining: "reste de la semaine"
68
+ },
69
+ {
70
+ unit: "month",
71
+ singular: "mois",
72
+ plural: "mois",
73
+ current: "ce mois-ci",
74
+ previous: "le mois dernier",
75
+ next: "le mois prochain",
76
+ toDate: "mois à ce jour",
77
+ remaining: "reste du mois"
78
+ },
79
+ {
80
+ unit: "quarter",
81
+ singular: "trimestre",
82
+ plural: "trimestres",
83
+ current: "ce trimestre",
84
+ previous: "le trimestre dernier",
85
+ next: "le trimestre prochain",
86
+ toDate: "trimestre à ce jour",
87
+ remaining: "reste du trimestre"
88
+ },
89
+ {
90
+ unit: "year",
91
+ singular: "an",
92
+ plural: "ans",
93
+ current: "cette année",
94
+ previous: "l'année dernière",
95
+ next: "l'année prochaine",
96
+ toDate: "année à ce jour",
97
+ remaining: "reste de l'année"
98
+ }
99
+ ];
100
+ const title = (value) => `${value.slice(0, 1).toLocaleUpperCase("fr")}${value.slice(1)}`;
101
+ const unitAliases = [
102
+ ["jour", "day"],
103
+ ["jours", "day"],
104
+ ["semaine", "week"],
105
+ ["semaines", "week"],
106
+ ["mois", "month"],
107
+ ["trimestre", "quarter"],
108
+ ["trimestres", "quarter"],
109
+ ["an", "year"],
110
+ ["ans", "year"],
111
+ ["année", "year"],
112
+ ["annee", "year"],
113
+ ["années", "year"],
114
+ ["annees", "year"]
115
+ ];
116
+ const periodAliases = [
117
+ ...units.flatMap((entry) => [
118
+ [
119
+ entry.current,
120
+ entry.unit,
121
+ 0,
122
+ entry.current
123
+ ],
124
+ [
125
+ entry.previous,
126
+ entry.unit,
127
+ -1,
128
+ entry.previous
129
+ ],
130
+ [
131
+ entry.next,
132
+ entry.unit,
133
+ 1,
134
+ entry.next
135
+ ]
136
+ ]),
137
+ [
138
+ "semaine en cours",
139
+ "week",
140
+ 0,
141
+ "cette semaine"
142
+ ],
143
+ [
144
+ "semaine dernière",
145
+ "week",
146
+ -1,
147
+ "la semaine dernière"
148
+ ],
149
+ [
150
+ "semaine derniere",
151
+ "week",
152
+ -1,
153
+ "la semaine dernière"
154
+ ],
155
+ [
156
+ "semaine suivante",
157
+ "week",
158
+ 1,
159
+ "la semaine prochaine"
160
+ ],
161
+ [
162
+ "le mois en cours",
163
+ "month",
164
+ 0,
165
+ "ce mois-ci"
166
+ ],
167
+ [
168
+ "ce mois",
169
+ "month",
170
+ 0,
171
+ "ce mois-ci"
172
+ ],
173
+ [
174
+ "mois dernier",
175
+ "month",
176
+ -1,
177
+ "le mois dernier"
178
+ ],
179
+ [
180
+ "mois prochain",
181
+ "month",
182
+ 1,
183
+ "le mois prochain"
184
+ ],
185
+ [
186
+ "mois à venir",
187
+ "month",
188
+ 1,
189
+ "le mois prochain"
190
+ ],
191
+ [
192
+ "l'année en cours",
193
+ "year",
194
+ 0,
195
+ "cette année"
196
+ ],
197
+ [
198
+ "cette annee",
199
+ "year",
200
+ 0,
201
+ "cette année"
202
+ ],
203
+ [
204
+ "année dernière",
205
+ "year",
206
+ -1,
207
+ "l'année dernière"
208
+ ],
209
+ [
210
+ "annee derniere",
211
+ "year",
212
+ -1,
213
+ "l'année dernière"
214
+ ],
215
+ [
216
+ "année passée",
217
+ "year",
218
+ -1,
219
+ "l'année dernière"
220
+ ],
221
+ [
222
+ "année prochaine",
223
+ "year",
224
+ 1,
225
+ "l'année prochaine"
226
+ ],
227
+ [
228
+ "annee prochaine",
229
+ "year",
230
+ 1,
231
+ "l'année prochaine"
232
+ ],
233
+ [
234
+ "trimestre dernier",
235
+ "quarter",
236
+ -1,
237
+ "le trimestre dernier"
238
+ ],
239
+ [
240
+ "trimestre prochain",
241
+ "quarter",
242
+ 1,
243
+ "le trimestre prochain"
244
+ ],
245
+ [
246
+ "avant-hier",
247
+ "day",
248
+ -2,
249
+ "avant-hier"
250
+ ],
251
+ [
252
+ "après-demain",
253
+ "day",
254
+ 2,
255
+ "après-demain"
256
+ ],
257
+ [
258
+ "apres-demain",
259
+ "day",
260
+ 2,
261
+ "après-demain"
262
+ ],
263
+ [
264
+ "l'avant-dernière semaine",
265
+ "week",
266
+ -2,
267
+ "l'avant-dernière semaine"
268
+ ],
269
+ [
270
+ "la semaine après la prochaine",
271
+ "week",
272
+ 2,
273
+ "la semaine après la prochaine"
274
+ ],
275
+ [
276
+ "le mois avant le dernier",
277
+ "month",
278
+ -2,
279
+ "le mois avant le dernier"
280
+ ],
281
+ [
282
+ "le mois après le prochain",
283
+ "month",
284
+ 2,
285
+ "le mois après le prochain"
286
+ ],
287
+ [
288
+ "le trimestre avant le dernier",
289
+ "quarter",
290
+ -2,
291
+ "le trimestre avant le dernier"
292
+ ],
293
+ [
294
+ "le trimestre après le prochain",
295
+ "quarter",
296
+ 2,
297
+ "le trimestre après le prochain"
298
+ ],
299
+ [
300
+ "l'année avant la dernière",
301
+ "year",
302
+ -2,
303
+ "l'année avant la dernière"
304
+ ],
305
+ [
306
+ "l'année après la prochaine",
307
+ "year",
308
+ 2,
309
+ "l'année après la prochaine"
310
+ ]
311
+ ];
312
+ const periodArticle = {
313
+ day: "de la journée",
314
+ week: "de la semaine",
315
+ month: "du mois",
316
+ quarter: "du trimestre",
317
+ year: "de l'année"
318
+ };
319
+ const toDatePhrases = units.flatMap((entry) => {
320
+ const yearAliases = entry.unit === "year" ? ["depuis le début d'année", "depuis le début de l'année en cours"] : [];
321
+ return [
322
+ entry.toDate,
323
+ `depuis le début ${periodArticle[entry.unit]}`,
324
+ `depuis le commencement ${periodArticle[entry.unit]}`,
325
+ `${entry.current} jusqu'à maintenant`,
326
+ ...yearAliases
327
+ ].map((phrase) => ({
328
+ entry,
329
+ phrase
330
+ }));
331
+ });
332
+ const remainingAliases = {
333
+ day: ["reste d'aujourd'hui"],
334
+ week: ["reste de cette semaine", "reste de la semaine en cours"],
335
+ month: ["reste de ce mois", "reste de ce mois-ci"],
336
+ quarter: ["reste de ce trimestre"],
337
+ year: ["reste de cette année", "reste de l'année en cours"]
338
+ };
339
+ const remainingPhrases = units.flatMap((entry) => [
340
+ entry.remaining,
341
+ `ce qui reste ${periodArticle[entry.unit]}`,
342
+ ...remainingAliases[entry.unit]
343
+ ].map((phrase) => ({
344
+ entry,
345
+ phrase
346
+ })));
347
+ const relativeYearDirection = (value) => {
348
+ if (value.includes("derni") || value.includes("pass")) return -1;
349
+ if (value.includes("prochain")) return 1;
350
+ return 0;
351
+ };
352
+ const relativeYearName = (direction) => {
353
+ if (direction < 0) return "l'année dernière";
354
+ if (direction > 0) return "l'année prochaine";
355
+ return "cette année";
356
+ };
357
+ const monthNumber = (value) => {
358
+ const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
359
+ const full = months.findIndex((month) => month === normalized);
360
+ if (full !== -1) return full + 1;
361
+ const short = monthAbbreviations.findIndex((aliases) => aliases.some((alias) => alias === normalized));
362
+ return short === -1 ? void 0 : short + 1;
363
+ };
364
+ const dateLabel = (day, month, year) => `${day === 1 ? "1er" : day} ${textAt(months, month - 1)} ${year}`;
365
+ const parseNamedDate = (input) => {
366
+ const named = String$1.match(/^(?:le )?(1er|[0-3]?\d)(?: de)? ([a-zàâçéèêëîïôûùüÿœ]+\.?)(?: de)? (\d{4})$/u)(input);
367
+ if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1).replace(/er$/u, ""), monthNumber, dateLabel);
368
+ const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
369
+ if (Option.isNone(numeric)) return Option.none();
370
+ const year = validYear(textAt(numeric.value, 3));
371
+ const month = Number(textAt(numeric.value, 2));
372
+ const day = Number(textAt(numeric.value, 1));
373
+ if (year === void 0 || month < 1 || month > 12) return Option.none();
374
+ const value = isoDate(year, month, day);
375
+ return isIsoDate(value) && value !== "9999-12-31" ? Option.some(fixedDatePeriod(value, dateLabel(day, month, year))) : Option.none();
376
+ };
377
+ const quarterNumber = (value) => {
378
+ if ((value.startsWith("q") || value.startsWith("t")) && value.length === 2) return Number(value.slice(1));
379
+ if (value === "1er" || value === "premier") return 1;
380
+ if (value === "2e" || value === "2ème" || value === "deuxième" || value === "deuxieme") return 2;
381
+ if (value === "3e" || value === "3ème" || value === "troisième" || value === "troisieme") return 3;
382
+ return value === "4e" || value === "4ème" || value === "quatrième" || value === "quatrieme" ? 4 : void 0;
383
+ };
384
+ const parseQuarter = (input) => {
385
+ 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);
386
+ if (Option.isSome(fixed)) {
387
+ const quarter = quarterNumber(textAt(fixed.value, 1));
388
+ const year = validYear(textAt(fixed.value, 2));
389
+ if (quarter !== void 0 && year !== void 0) return Option.some(fixedQuarterPeriod(year, quarter, `T${quarter} ${year}`));
390
+ }
391
+ 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);
392
+ if (Option.isSome(relative)) {
393
+ const quarter = quarterNumber(textAt(relative.value, 1));
394
+ const direction = relativeYearDirection(textAt(relative.value, 2));
395
+ if (quarter !== void 0) return Option.some(quarterOfRelativeYear(quarter, direction, `T${quarter} de ${relativeYearName(direction)}`));
396
+ }
397
+ const standalone = String$1.match(/^(?:le )?(t[1-4]|q[1-4])$/u)(input);
398
+ if (Option.isNone(standalone)) return Option.none();
399
+ const quarter = quarterNumber(textAt(standalone.value, 1));
400
+ return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `T${quarter}`));
401
+ };
402
+ const parseBasePeriod = (input) => {
403
+ if (isIsoDate(input) && input !== "9999-12-31") return Option.some(fixedDatePeriod(input, input));
404
+ const namedDate = parseNamedDate(input);
405
+ if (Option.isSome(namedDate)) return namedDate;
406
+ const quarter = parseQuarter(input);
407
+ if (Option.isSome(quarter)) return quarter;
408
+ const yearMatch = String$1.match(/^(?:(?:l')?année |annee )?(\d{4})$/u)(input);
409
+ if (Option.isSome(yearMatch)) {
410
+ const year = validYear(textAt(yearMatch.value, 1));
411
+ if (year !== void 0) return Option.some(fixedYearPeriod(year, String(year)));
412
+ }
413
+ const monthYear = String$1.match(/^([a-zàâçéèêëîïôûùüÿœ]+\.?)(?: de)? (\d{4})$/u)(input);
414
+ if (Option.isSome(monthYear)) {
415
+ const month = monthNumber(textAt(monthYear.value, 1));
416
+ const year = validYear(textAt(monthYear.value, 2));
417
+ if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
418
+ }
419
+ 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);
420
+ if (Option.isSome(relativeMonth)) {
421
+ const month = monthNumber(textAt(relativeMonth.value, 1));
422
+ const yearText = textAt(relativeMonth.value, 2);
423
+ const direction = relativeYearDirection(yearText);
424
+ if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${title(textAt(months, month - 1))} de ${relativeYearName(direction)}`));
425
+ }
426
+ const standaloneMonth = monthNumber(input);
427
+ if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(textAt(months, standaloneMonth - 1))));
428
+ const alias = periodAliases.find((entry) => entry[0] === input);
429
+ if (alias !== void 0) return Option.some(relativePeriod(alias[1], alias[2], alias[3]));
430
+ if ([
431
+ "week-end",
432
+ "le week-end",
433
+ "ce week-end",
434
+ "weekend",
435
+ "ce weekend"
436
+ ].includes(input)) return Option.some(relativeWeekend(0, "ce week-end"));
437
+ if ([
438
+ "le week-end dernier",
439
+ "week-end dernier",
440
+ "le weekend dernier"
441
+ ].includes(input)) return Option.some(relativeWeekend(-1, "le week-end dernier"));
442
+ if ([
443
+ "le week-end prochain",
444
+ "week-end prochain",
445
+ "le weekend prochain"
446
+ ].includes(input)) return Option.some(relativeWeekend(1, "le week-end prochain"));
447
+ if (input === "l'avant-dernier week-end") return Option.some(relativeWeekend(-2, input));
448
+ if (input === "le week-end après le prochain") return Option.some(relativeWeekend(2, input));
449
+ return Option.none();
450
+ };
451
+ const parsePeriod = (input) => {
452
+ const edge = String$1.match(/^(?:le |la |l')?(début|debut|commencement|fin)(?: du | de la | de l'| de )(.+)$/u)(input);
453
+ if (Option.isSome(edge)) {
454
+ const edgeName = textAt(edge.value, 1);
455
+ const period = parseBasePeriod(textAt(edge.value, 2));
456
+ if (Option.isSome(period)) {
457
+ const isEnd = edgeName === "fin";
458
+ const canonical = `${isEnd ? "fin" : "début"} de ${period.value.canonical}`;
459
+ return Option.some(isEnd ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
460
+ }
461
+ }
462
+ const wrapper = [
463
+ "pendant ",
464
+ "en ",
465
+ "tout ",
466
+ "tout le ",
467
+ "toute la "
468
+ ].find((prefix) => input.startsWith(prefix));
469
+ return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
470
+ };
471
+ const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
472
+ const countedUnitPattern = "jour|jours|semaine|semaines|mois|trimestre|trimestres|an|ans|année|annee|années|annees";
473
+ const compileCountedPattern = (source) => new RegExp(source.replace("UNIT", countedUnitPattern), "u");
474
+ const calendarPastPattern = compileCountedPattern("^il y a ([1-9]\\d*) (UNIT)$");
475
+ const calendarFuturePattern = compileCountedPattern("^dans ([1-9]\\d*) (UNIT)$");
476
+ const rollingSincePattern = compileCountedPattern("^depuis ([1-9]\\d*) (UNIT)$");
477
+ const rollingBarePattern = compileCountedPattern("^([1-9]\\d*) (UNIT)$");
478
+ const rollingPastPatterns = [
479
+ compileCountedPattern("^(?:(?:les|la) )?(?:derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes) ([1-9]\\d*) (UNIT)$"),
480
+ compileCountedPattern("^(?:(?:les|la) )?([1-9]\\d*) (?:derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes) (UNIT)$"),
481
+ compileCountedPattern("^([1-9]\\d*) (UNIT) (?:derniers|dernières|dernieres|passés|passées|passes|précédents|précédentes|precedents|precedentes)$")
482
+ ];
483
+ const rollingFuturePatterns = [
484
+ compileCountedPattern("^(?:(?:les|la) )?(?:prochains|prochaines|suivants|suivantes) ([1-9]\\d*) (UNIT)$"),
485
+ compileCountedPattern("^(?:(?:les|la) )?([1-9]\\d*) (?:prochains|prochaines|suivants|suivantes) (UNIT)$"),
486
+ compileCountedPattern("^([1-9]\\d*) (UNIT) (?:prochains|prochaines|suivants|suivantes|à venir)$")
487
+ ];
488
+ const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
489
+ const isFeminineUnit = (unit) => unit === "week";
490
+ const relativeCountPhrase = (amount, entry, future) => {
491
+ const noun = amount === 1 ? entry.singular : entry.plural;
492
+ let modifier = future ? "prochains" : "derniers";
493
+ if (isFeminineUnit(entry.unit)) modifier = future ? "prochaines" : "dernières";
494
+ return `les ${amount} ${modifier} ${noun}`;
495
+ };
496
+ const parseCalendarOffset = (input) => {
497
+ const past = String$1.match(calendarPastPattern)(input);
498
+ const future = String$1.match(calendarFuturePattern)(input);
499
+ const match = Option.firstSomeOf([past, future]);
500
+ if (Option.isNone(match)) return Option.none();
501
+ const amount = parseTrailingCount(textAt(match.value, 1));
502
+ const unit = countedUnit(textAt(match.value, 2));
503
+ if (Option.isNone(amount) || unit === void 0) return Option.none();
504
+ const entry = units.find((item) => item.unit === unit);
505
+ if (entry === void 0) return Option.none();
506
+ const direction = Option.isSome(past) ? -amount.value : amount.value;
507
+ const noun = amount.value === 1 ? entry.singular : entry.plural;
508
+ const canonical = direction < 0 ? `il y a ${amount.value} ${noun}` : `dans ${amount.value} ${noun}`;
509
+ return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
510
+ };
511
+ const parseRollingPeriod = (input) => {
512
+ const since = String$1.match(rollingSincePattern)(input);
513
+ const past = firstPatternMatch(input, rollingPastPatterns);
514
+ const bare = String$1.match(rollingBarePattern)(input);
515
+ const future = firstPatternMatch(input, rollingFuturePatterns);
516
+ const match = Option.firstSomeOf([
517
+ since,
518
+ past,
519
+ bare,
520
+ future
521
+ ]);
522
+ if (Option.isNone(match)) return Option.none();
523
+ const amount = parseTrailingCount(textAt(match.value, 1));
524
+ const unit = countedUnit(textAt(match.value, 2));
525
+ if (Option.isNone(amount) || unit === void 0) return Option.none();
526
+ const entry = units.find((item) => item.unit === unit);
527
+ if (entry === void 0) return Option.none();
528
+ const isFuture = Option.isSome(future);
529
+ const range = isFuture ? futureRange(amount.value, unit) : trailingRange(amount.value, unit);
530
+ return Option.some(candidate(range, relativeCountPhrase(amount.value, entry, isFuture)));
531
+ };
532
+ const boundaryCandidate = (input) => {
533
+ const included = String$1.match(/^jusqu'à (.+) (?:inclus|incluse|inclusivement)$/u)(input);
534
+ if (Option.isSome(included)) {
535
+ const period = parsePeriod(textAt(included.value, 1));
536
+ if (Option.isSome(period)) return Option.some(periodBoundaryCandidate(period.value, "through", `jusqu'à ${period.value.canonical}`));
537
+ }
538
+ return openBoundaryCandidate(input, [
539
+ ["jusqu'avant ", "before"],
540
+ ["jusqu'au début de ", "before"],
541
+ ["jusqu'à et y compris ", "through"],
542
+ ["à partir de ", "since"],
543
+ ["depuis ", "since"],
544
+ ["avant ", "before"],
545
+ ["jusqu'à ", "through"],
546
+ ["jusqu'au ", "through"],
547
+ ["jusqu'en ", "through"],
548
+ ["après ", "after"],
549
+ ["apres ", "after"]
550
+ ], parsePeriod);
551
+ };
552
+ const parseFrench = (input) => {
553
+ const remaining = remainingPhrases.find((entry) => entry.phrase === input);
554
+ if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry.unit), remaining.entry.remaining));
555
+ if ([
556
+ "jusqu'à aujourd'hui",
557
+ "jusqu'à maintenant",
558
+ "à ce jour"
559
+ ].includes(input)) return Option.some(candidate(untilNowRange(), "jusqu'à maintenant"));
560
+ if ([
561
+ "depuis maintenant",
562
+ "à partir de maintenant",
563
+ "désormais"
564
+ ].includes(input)) return Option.some(candidate(fromNowRange(), "depuis maintenant"));
565
+ const offset = parseCalendarOffset(input);
566
+ if (Option.isSome(offset)) return offset;
567
+ const rolling = parseRollingPeriod(input);
568
+ if (Option.isSome(rolling)) return rolling;
569
+ const toDate = toDatePhrases.find((entry) => entry.phrase === input);
570
+ if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
571
+ const nowBounded = joinedNowCandidate(input, [
572
+ ["depuis ", " jusqu'à maintenant"],
573
+ ["depuis ", " jusqu'à aujourd'hui"],
574
+ ["entre ", " et aujourd'hui"]
575
+ ], [
576
+ "d'aujourd'hui à ",
577
+ "de maintenant à ",
578
+ "entre aujourd'hui et "
579
+ ], parsePeriod, (period) => `depuis ${period} jusqu'à maintenant`, (period) => `de maintenant à ${period}`);
580
+ if (Option.isSome(nowBounded)) return nowBounded;
581
+ const bounded = joinedPeriodCandidate(input, [
582
+ ["depuis ", " jusqu'à "],
583
+ ["du ", " au "],
584
+ ["de ", " à "],
585
+ ["entre ", " et "],
586
+ ["", " - "],
587
+ ["", " – "],
588
+ ["", " — "],
589
+ ["", " jusqu'à "],
590
+ ["", " au "]
591
+ ], parsePeriod, (lower, upper) => `du ${lower} au ${upper}`);
592
+ if (Option.isSome(bounded)) return bounded;
593
+ const boundary = boundaryCandidate(input);
594
+ if (Option.isSome(boundary)) return boundary;
595
+ return Option.map(parsePeriod(input), (period) => candidate(periodRange(period), period.canonical));
596
+ };
597
+ const staticPeriodPhrases = [
598
+ ...periodAliases.map((entry) => entry[0]),
599
+ "ce week-end",
600
+ "le week-end dernier",
601
+ "le week-end prochain",
602
+ "l'avant-dernier week-end",
603
+ "le week-end après le prochain",
604
+ ...periodAliases.flatMap((entry) => [`début de ${entry[0]}`, `fin de ${entry[0]}`]),
605
+ ...[
606
+ 1,
607
+ 2,
608
+ 3,
609
+ 4
610
+ ].flatMap((quarter) => [
611
+ `t${quarter}`,
612
+ `t${quarter} de cette année`,
613
+ `t${quarter} de l'année dernière`,
614
+ `t${quarter} de l'année prochaine`
615
+ ]),
616
+ ...months.flatMap((month) => [
617
+ month,
618
+ `${month} de l'année dernière`,
619
+ `${month} de l'année prochaine`
620
+ ])
621
+ ];
622
+ const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
623
+ const boundaryPrefixes = [
624
+ "depuis ",
625
+ "avant ",
626
+ "jusqu'à ",
627
+ "après "
628
+ ];
629
+ const countedSuggestions = (input) => {
630
+ const amount = naturalCount(input);
631
+ if (amount === void 0) return [];
632
+ return units.flatMap((entry) => {
633
+ const noun = amount === 1 ? entry.singular : entry.plural;
634
+ const past = isFeminineUnit(entry.unit) ? "dernières" : "derniers";
635
+ const future = isFeminineUnit(entry.unit) ? "prochaines" : "prochains";
636
+ return [
637
+ relativeCountPhrase(amount, entry, false),
638
+ `${amount} ${past} ${noun}`,
639
+ `${amount} ${noun}`,
640
+ relativeCountPhrase(amount, entry, true),
641
+ `${amount} ${future} ${noun}`,
642
+ `${amount} ${noun} à venir`,
643
+ `il y a ${amount} ${noun}`,
644
+ `dans ${amount} ${noun}`
645
+ ];
646
+ });
647
+ };
648
+ const frenchSuggestionPhrases = [
649
+ ...units.map((entry) => entry.toDate),
650
+ ...units.map((entry) => entry.remaining),
651
+ ...staticPeriodPhrases,
652
+ ...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
653
+ "jusqu'à maintenant",
654
+ "depuis maintenant"
655
+ ];
656
+ const suggestFrench = (input, limit) => {
657
+ const fixed = fixedCalendarPeriodPhrases(input, months);
658
+ return completeNaturalPhrases(input, [
659
+ ...frenchSuggestionPhrases,
660
+ ...fixed,
661
+ ...prefixNaturalPhrases(fixed, boundaryPrefixes),
662
+ ...countedSuggestions(input)
663
+ ], limit);
664
+ };
665
+ const renderFrench = (range) => {
666
+ const offset = calendarPeriodOffset(range);
667
+ if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
668
+ const entry = units.find((unit) => unit.unit === offset.value.unit);
669
+ if (entry !== void 0) {
670
+ const amount = Math.abs(offset.value.amount);
671
+ const noun = amount === 1 ? entry.singular : entry.plural;
672
+ return Option.some(offset.value.amount < 0 ? `il y a ${amount} ${noun}` : `dans ${amount} ${noun}`);
673
+ }
674
+ }
675
+ const future = futurePeriod(range);
676
+ if (Option.isSome(future)) {
677
+ const entry = units.find((unit) => unit.unit === future.value.unit);
678
+ if (entry !== void 0) {
679
+ const noun = future.value.amount === 1 ? entry.singular : entry.plural;
680
+ return Option.some(`${future.value.amount} ${noun} à venir`);
681
+ }
682
+ }
683
+ const trailing = trailingPeriod(range);
684
+ if (Option.isSome(trailing)) {
685
+ const entry = units.find((unit) => unit.unit === trailing.value.unit);
686
+ if (entry !== void 0) return Option.some(relativeCountPhrase(trailing.value.amount, entry, false));
687
+ }
688
+ const periods = [...staticPeriods, ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)];
689
+ 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}`, (period) => `jusqu'à ${period}`, (period) => `après ${period}`, (lower, upper) => `du ${lower} au ${upper}`, (period) => `depuis ${period} jusqu'à maintenant`, (period) => `de maintenant à ${period}`, () => "jusqu'à maintenant", () => "depuis maintenant");
690
+ };
691
+ const normalizeFrench = (input, locale) => normalizeNaturalText(input, locale).replaceAll("’", "'");
692
+ const FrenchContribution = new BaseLanguageContribution({
693
+ locale: "fr",
694
+ vocabulary: [
695
+ ...months,
696
+ ...monthAbbreviations.flatMap((aliases) => aliases),
697
+ ...units.flatMap((entry) => [
698
+ entry.singular,
699
+ entry.plural,
700
+ ...entry.current.split(" "),
701
+ ...entry.previous.split(" "),
702
+ ...entry.next.split(" ")
703
+ ]),
704
+ ...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
705
+ ...remainingPhrases.flatMap((entry) => entry.phrase.split(" ")),
706
+ "année",
707
+ "annee",
708
+ "après",
709
+ "avant",
710
+ "commencement",
711
+ "dans",
712
+ "depuis",
713
+ "dernier",
714
+ "début",
715
+ "entre",
716
+ "fin",
717
+ "inclus",
718
+ "maintenant",
719
+ "passé",
720
+ "précédent",
721
+ "prochain",
722
+ "reste",
723
+ "jusqu'à"
724
+ ],
725
+ normalize: normalizeFrench,
726
+ correct: correctWhitespaceSeparatedText,
727
+ parseExact: parseFrench,
728
+ suggest: suggestFrench,
729
+ render: renderFrench
730
+ });
731
+ const FrenchLanguage = defineLanguagePlugin({
732
+ id: "chronolizer/language-fr",
733
+ effect: (context) => Effect.asVoid(context.register("chronolizer/language-fr", FrenchContribution))
734
+ });
735
+ const FrenchLanguageLayer = languagePluginsLayer([FrenchLanguage]);
736
+ //#endregion
737
+ export { FrenchContribution, FrenchLanguage, FrenchLanguageLayer };