chronolizer 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,8 @@ import { BaseLanguageContribution } from "../language/model.mjs";
3
3
  import { normalizeNaturalText } from "../natural/text.mjs";
4
4
  import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
5
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, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
6
+ import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount } from "../natural/suggestion.mjs";
7
+ import { absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
8
8
  import { Effect, Option, String as String$1 } from "effect";
9
9
  //#region src/locales/pl.ts
10
10
  const months = [
@@ -21,6 +21,95 @@ const months = [
21
21
  "listopad",
22
22
  "grudzień"
23
23
  ];
24
+ const weekdays = [
25
+ "poniedziałek",
26
+ "wtorek",
27
+ "środa",
28
+ "czwartek",
29
+ "piątek",
30
+ "sobota",
31
+ "niedziela"
32
+ ];
33
+ const weekdayGenitives = [
34
+ "poniedziałku",
35
+ "wtorku",
36
+ "środy",
37
+ "czwartku",
38
+ "piątku",
39
+ "soboty",
40
+ "niedzieli"
41
+ ];
42
+ const nextWeekdays = weekdays.flatMap((weekday, day) => {
43
+ const feminine = day === 2 || day >= 5;
44
+ const canonical = `${feminine ? "następna" : "następny"} ${weekday}`;
45
+ return [{
46
+ phrase: canonical,
47
+ day,
48
+ canonical
49
+ }, {
50
+ phrase: `${feminine ? "następnej" : "następnego"} ${textAt(weekdayGenitives, day)}`,
51
+ day,
52
+ canonical
53
+ }];
54
+ });
55
+ const polishCountWords = [
56
+ [
57
+ "dwa",
58
+ "dwie",
59
+ "dwóch"
60
+ ],
61
+ ["trzy"],
62
+ ["cztery"],
63
+ ["pięć"],
64
+ ["sześć"],
65
+ ["siedem"],
66
+ ["osiem"],
67
+ ["dziewięć"],
68
+ ["dziesięć"],
69
+ ["jedenaście"],
70
+ ["dwanaście"],
71
+ ["trzynaście"],
72
+ ["czternaście"],
73
+ ["piętnaście"],
74
+ ["szesnaście"],
75
+ ["siedemnaście"],
76
+ ["osiemnaście"],
77
+ ["dziewiętnaście"],
78
+ ["dwadzieścia"]
79
+ ];
80
+ const polishCountOnes = [
81
+ [1, "jeden"],
82
+ [2, "dwa"],
83
+ [3, "trzy"],
84
+ [4, "cztery"],
85
+ [5, "pięć"],
86
+ [6, "sześć"],
87
+ [7, "siedem"],
88
+ [8, "osiem"],
89
+ [9, "dziewięć"]
90
+ ];
91
+ const polishCountAliases = [
92
+ ...sequentialCountAliases([[
93
+ "jeden",
94
+ "jedna",
95
+ "jedno"
96
+ ]], 1),
97
+ ...sequentialCountAliases(polishCountWords, 2),
98
+ ...compoundCountAliases(decimalTens([
99
+ "dwadzieścia",
100
+ "trzydzieści",
101
+ "czterdzieści",
102
+ "pięćdziesiąt",
103
+ "sześćdziesiąt",
104
+ "siedemdziesiąt",
105
+ "osiemdziesiąt",
106
+ "dziewięćdziesiąt"
107
+ ]), polishCountOnes, (ten, one) => [`${ten} ${one}`])
108
+ ];
109
+ const normalizePolishCounts = compileCountAliasNormalizer(polishCountAliases);
110
+ const polishCountVocabulary = new Set(countAliasVocabulary(polishCountAliases));
111
+ const correctPolish = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, polishCountVocabulary);
112
+ const normalizePolish = (input, locale) => normalizePolishCounts(normalizeNaturalText(input, locale));
24
113
  const monthGenitives = [
25
114
  "stycznia",
26
115
  "lutego",
@@ -86,8 +175,8 @@ const units = [
86
175
  current: "dzisiaj",
87
176
  previous: "wczoraj",
88
177
  next: "jutro",
89
- toDate: "dzisiaj do tej pory",
90
- remaining: "reszta dnia"
178
+ toDate: "od początku dnia",
179
+ remaining: "do końca tego dnia"
91
180
  },
92
181
  {
93
182
  unit: "week",
@@ -97,8 +186,8 @@ const units = [
97
186
  current: "ten tydzień",
98
187
  previous: "poprzedni tydzień",
99
188
  next: "następny tydzień",
100
- toDate: "tydzień do dziś",
101
- remaining: "reszta tygodnia"
189
+ toDate: "od początku tygodnia",
190
+ remaining: "do końca tego tygodnia"
102
191
  },
103
192
  {
104
193
  unit: "month",
@@ -108,8 +197,8 @@ const units = [
108
197
  current: "ten miesiąc",
109
198
  previous: "poprzedni miesiąc",
110
199
  next: "następny miesiąc",
111
- toDate: "miesiąc do dziś",
112
- remaining: "reszta miesiąca"
200
+ toDate: "od początku miesiąca",
201
+ remaining: "do końca tego miesiąca"
113
202
  },
114
203
  {
115
204
  unit: "quarter",
@@ -119,8 +208,8 @@ const units = [
119
208
  current: "ten kwartał",
120
209
  previous: "poprzedni kwartał",
121
210
  next: "następny kwartał",
122
- toDate: "kwartał do dziś",
123
- remaining: "reszta kwartału"
211
+ toDate: "od początku kwartału",
212
+ remaining: "do końca tego kwartału"
124
213
  },
125
214
  {
126
215
  unit: "year",
@@ -130,8 +219,8 @@ const units = [
130
219
  current: "ten rok",
131
220
  previous: "poprzedni rok",
132
221
  next: "następny rok",
133
- toDate: "rok do dziś",
134
- remaining: "reszta roku"
222
+ toDate: "od początku roku",
223
+ remaining: "do końca tego roku"
135
224
  }
136
225
  ];
137
226
  const title = (value) => `${value.slice(0, 1).toLocaleUpperCase("pl")}${value.slice(1)}`;
@@ -151,6 +240,64 @@ const unitAliases = [
151
240
  ["lata", "year"],
152
241
  ["lat", "year"]
153
242
  ];
243
+ const declinedUnits = [
244
+ [
245
+ "week",
246
+ "tydzień",
247
+ "tygodnia",
248
+ "tygodniem",
249
+ "tygodniu"
250
+ ],
251
+ [
252
+ "month",
253
+ "miesiąc",
254
+ "miesiąca",
255
+ "miesiącem",
256
+ "miesiącu"
257
+ ],
258
+ [
259
+ "quarter",
260
+ "kwartał",
261
+ "kwartału",
262
+ "kwartałem",
263
+ "kwartale"
264
+ ],
265
+ [
266
+ "year",
267
+ "rok",
268
+ "roku",
269
+ "rokiem",
270
+ "roku"
271
+ ]
272
+ ];
273
+ const relativeAdjectives = [
274
+ [
275
+ 0,
276
+ "ten",
277
+ "tego",
278
+ "tym"
279
+ ],
280
+ [
281
+ -1,
282
+ "poprzedni",
283
+ "poprzedniego",
284
+ "poprzednim"
285
+ ],
286
+ [
287
+ 1,
288
+ "następny",
289
+ "następnego",
290
+ "następnym"
291
+ ]
292
+ ];
293
+ const declinedPeriods = declinedUnits.flatMap(([unit, nominativeNoun, genitiveNoun, instrumentalNoun, locativeNoun]) => relativeAdjectives.map(([direction, nominative, genitive, oblique]) => [
294
+ `${nominative} ${nominativeNoun}`,
295
+ unit,
296
+ direction,
297
+ `${genitive} ${genitiveNoun}`,
298
+ `${oblique} ${instrumentalNoun}`,
299
+ `${oblique} ${locativeNoun}`
300
+ ]));
154
301
  const periodAliases = [
155
302
  ...units.flatMap((entry) => [
156
303
  [
@@ -220,77 +367,55 @@ const periodAliases = [
220
367
  1,
221
368
  "następny rok"
222
369
  ],
370
+ ...declinedPeriods.flatMap(([canonical, unit, direction, genitive, instrumental, locative]) => [
371
+ [
372
+ genitive,
373
+ unit,
374
+ direction,
375
+ canonical
376
+ ],
377
+ [
378
+ instrumental,
379
+ unit,
380
+ direction,
381
+ canonical
382
+ ],
383
+ [
384
+ locative,
385
+ unit,
386
+ direction,
387
+ canonical
388
+ ]
389
+ ]),
223
390
  [
224
- "tego tygodnia",
225
- "week",
226
- 0,
227
- "ten tydzień"
228
- ],
229
- [
230
- "poprzedniego tygodnia",
391
+ "zeszłym tygodniu",
231
392
  "week",
232
393
  -1,
233
394
  "poprzedni tydzień"
234
395
  ],
235
396
  [
236
- "następnego tygodnia",
237
- "week",
238
- 1,
239
- "następny tydzień"
240
- ],
241
- [
242
- "tego miesiąca",
243
- "month",
244
- 0,
245
- "ten miesiąc"
246
- ],
247
- [
248
- "poprzedniego miesiąca",
397
+ "zeszłym miesiącu",
249
398
  "month",
250
399
  -1,
251
400
  "poprzedni miesiąc"
252
401
  ],
253
402
  [
254
- "następnego miesiąca",
255
- "month",
256
- 1,
257
- "następny miesiąc"
258
- ],
259
- [
260
- "tego kwartału",
261
- "quarter",
262
- 0,
263
- "ten kwartał"
264
- ],
265
- [
266
- "poprzedniego kwartału",
403
+ "zeszłym kwartale",
267
404
  "quarter",
268
405
  -1,
269
406
  "poprzedni kwartał"
270
407
  ],
271
408
  [
272
- "następnego kwartału",
273
- "quarter",
274
- 1,
275
- "następny kwartał"
276
- ],
277
- [
278
- "tego roku",
279
- "year",
280
- 0,
281
- "ten rok"
282
- ],
283
- [
284
- "poprzedniego roku",
409
+ "zeszłym roku",
285
410
  "year",
286
411
  -1,
287
412
  "poprzedni rok"
288
413
  ],
289
414
  [
290
- "następnego roku",
291
- "year",
292
- 1,
293
- "następny rok"
415
+ "dziś",
416
+ "day",
417
+ 0,
418
+ "dzisiaj"
294
419
  ],
295
420
  [
296
421
  "przedwczoraj",
@@ -341,21 +466,33 @@ const periodAliases = [
341
466
  "rok po następnym"
342
467
  ]
343
468
  ];
344
- const toDatePhrases = units.flatMap((entry) => {
345
- const yearAliases = entry.unit === "year" ? [
346
- "od początku roku",
347
- "od początku roku do dziś",
348
- "w tym roku do dziś"
349
- ] : [];
350
- return [entry.toDate, ...yearAliases].map((phrase) => ({
469
+ const legacyToDatePhrases = [
470
+ "dzisiaj do tej pory",
471
+ "tydzień do dziś",
472
+ "miesiąc do dziś",
473
+ "kwartał do dziś",
474
+ "rok do dziś"
475
+ ];
476
+ const toDatePhrases = units.flatMap((entry, index) => {
477
+ const aliases = [entry.toDate, textAt(legacyToDatePhrases, index)];
478
+ if (entry.unit === "year") aliases.push("od początku roku do dziś", "w tym roku do dziś");
479
+ return aliases.map((phrase) => ({
480
+ entry,
481
+ phrase
482
+ }));
483
+ });
484
+ const remainingPhrases = units.flatMap((entry) => {
485
+ const implicit = entry.remaining.replace("tego ", "");
486
+ const legacy = entry.remaining.replace("do końca tego", "reszta");
487
+ return [
488
+ entry.remaining,
489
+ implicit,
490
+ legacy
491
+ ].map((phrase) => ({
351
492
  entry,
352
493
  phrase
353
494
  }));
354
495
  });
355
- const remainingPhrases = units.map((entry) => ({
356
- entry,
357
- phrase: entry.remaining
358
- }));
359
496
  const relativeYearDirection = (value) => {
360
497
  if (value.includes("poprzed")) return -1;
361
498
  if (value.includes("następn")) return 1;
@@ -374,23 +511,42 @@ const monthNumber = (value) => {
374
511
  return short === -1 ? void 0 : short + 1;
375
512
  };
376
513
  const dateLabel = (day, month, year) => `${day}. ${textAt(monthGenitives, month - 1)} ${year}`;
514
+ const currentDateLabel = (day, month) => `${day}. ${textAt(monthGenitives, month - 1)}`;
377
515
  const withMonthCase = (value, forms) => {
378
516
  const month = months.findIndex((name) => value.startsWith(title(name)) || value.startsWith(name));
379
517
  if (month === -1) return value;
380
518
  const name = textAt(months, month);
381
519
  return `${textAt(forms, month)}${value.slice(name.length)}`;
382
520
  };
521
+ const periodCaseIndexes = {
522
+ genitive: 3,
523
+ instrumental: 4,
524
+ locative: 5
525
+ };
526
+ const monthCases = {
527
+ genitive: monthGenitives,
528
+ instrumental: monthInstrumentals,
529
+ locative: monthLocatives
530
+ };
531
+ const withPeriodCase = (value, grammaticalCase) => {
532
+ const period = declinedPeriods.find((entry) => entry[0] === value);
533
+ return period === void 0 ? withMonthCase(value, monthCases[grammaticalCase]) : period[periodCaseIndexes[grammaticalCase]];
534
+ };
383
535
  const parseNamedDate = (input) => {
384
536
  const named = String$1.match(/^([0-3]?\d)\.?(?: )([a-ząćęłńóśźż]+\.?) (\d{4})$/u)(input);
385
537
  if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
386
538
  const numeric = String$1.match(/^([0-3]?\d)(?:\. ?|[/-])([01]?\d)(?:\. ?|[/-])(\d{4})$/u)(input);
387
- if (Option.isNone(numeric)) return Option.none();
388
- const year = validYear(textAt(numeric.value, 3));
389
- const month = Number(textAt(numeric.value, 2));
390
- const day = Number(textAt(numeric.value, 1));
391
- if (year === void 0 || month < 1 || month > 12) return Option.none();
392
- const value = isoDate(year, month, day);
393
- return isIsoDate(value) && value !== "9999-12-31" ? Option.some(fixedDatePeriod(value, dateLabel(day, month, year))) : Option.none();
539
+ if (Option.isSome(numeric)) {
540
+ const year = validYear(textAt(numeric.value, 3));
541
+ const month = Number(textAt(numeric.value, 2));
542
+ const day = Number(textAt(numeric.value, 1));
543
+ if (year !== void 0 && month >= 1 && month <= 12) {
544
+ const value = isoDate(year, month, day);
545
+ if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
546
+ }
547
+ }
548
+ const current = String$1.match(/^([0-3]?\d)\.? ([a-ząćęłńóśźż]+\.?)$/u)(input);
549
+ return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
394
550
  };
395
551
  const quarterNumber = (value) => {
396
552
  if (value.startsWith("q") && value.length === 2) return Number(value.slice(1));
@@ -417,12 +573,13 @@ const parseQuarter = (input) => {
417
573
  const quarter = quarterNumber(textAt(standalone.value, 1));
418
574
  return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
419
575
  };
420
- const parseBasePeriod = (input) => {
421
- if (isIsoDate(input) && input !== "9999-12-31") return Option.some(fixedDatePeriod(input, input));
422
- const namedDate = parseNamedDate(input);
423
- if (Option.isSome(namedDate)) return namedDate;
424
- const quarter = parseQuarter(input);
425
- if (Option.isSome(quarter)) return quarter;
576
+ const parseDatedPeriod = (input) => {
577
+ const knownPeriod = Option.firstSomeOf([
578
+ absoluteDatePeriod(input, "pl"),
579
+ parseNamedDate(input),
580
+ parseQuarter(input)
581
+ ]);
582
+ if (Option.isSome(knownPeriod)) return knownPeriod;
426
583
  const yearMatch = String$1.match(/^(?:rok |roku )?(\d{4})$/u)(input);
427
584
  if (Option.isSome(yearMatch)) {
428
585
  const year = validYear(textAt(yearMatch.value, 1));
@@ -434,6 +591,20 @@ const parseBasePeriod = (input) => {
434
591
  const year = validYear(textAt(monthYear.value, 2));
435
592
  if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
436
593
  }
594
+ return Option.none();
595
+ };
596
+ const parseBasePeriod = (input) => {
597
+ const datedPeriod = parseDatedPeriod(input);
598
+ if (Option.isSome(datedPeriod)) return datedPeriod;
599
+ const prefixedRelativeMonth = String$1.match(/^(poprzedni|ten|następny) ([a-ząćęłńóśźż]+\.?)$/u)(input);
600
+ if (Option.isSome(prefixedRelativeMonth)) {
601
+ const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
602
+ if (month !== void 0) {
603
+ const modifier = textAt(prefixedRelativeMonth.value, 1);
604
+ const direction = relativeYearDirection(modifier);
605
+ return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${textAt(months, month - 1)}`));
606
+ }
607
+ }
437
608
  const relativeMonth = String$1.match(/^([a-ząćęłńóśźż]+\.?) (poprzedniego roku|następnego roku|tego roku)$/u)(input);
438
609
  const relativeMonthYearFirst = String$1.match(/^(poprzedniego roku|następnego roku|tego roku) ([a-ząćęłńóśźż]+\.?)$/u)(input);
439
610
  const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
@@ -454,33 +625,54 @@ const parseBasePeriod = (input) => {
454
625
  if (["następny weekend", "przyszły weekend"].includes(input)) return Option.some(relativeWeekend(1, "następny weekend"));
455
626
  if (input === "weekend przed poprzednim") return Option.some(relativeWeekend(-2, input));
456
627
  if (input === "weekend po następnym") return Option.some(relativeWeekend(2, input));
457
- return Option.none();
628
+ const weekday = nextWeekdays.find((entry) => entry.phrase === input);
629
+ return weekday === void 0 ? Option.none() : Option.some(relativeWeekday(weekday.day, 1, weekday.canonical));
458
630
  };
459
631
  const parsePeriod = (input) => {
632
+ const shifted = String$1.match(/^(.+) (?:([1-9]\d*) (dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat) (temu)|za ([1-9]\d*) (dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat))$/u)(input);
633
+ if (Option.isSome(shifted)) {
634
+ const past = textAt(shifted.value, 4) === "temu";
635
+ const amount = parseTrailingCount(textAt(shifted.value, past ? 2 : 5));
636
+ const alias = unitAliases.find((unit) => unit[0] === textAt(shifted.value, past ? 3 : 6));
637
+ const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
638
+ const period = parsePeriod(textAt(shifted.value, 1));
639
+ if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
640
+ const direction = past ? -amount.value : amount.value;
641
+ const noun = countNoun(amount.value, entry);
642
+ const canonical = past ? `${period.value.canonical} ${amount.value} ${noun} temu` : `${period.value.canonical} za ${amount.value} ${noun}`;
643
+ return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
644
+ }
645
+ }
460
646
  const edge = String$1.match(/^(początek|koniec) (.+)$/u)(input);
461
647
  if (Option.isSome(edge)) {
462
- const period = parseBasePeriod(textAt(edge.value, 2));
648
+ const periodText = textAt(edge.value, 2);
649
+ const basePeriod = parseBasePeriod(periodText);
650
+ const implicitUnit = declinedUnits.find((entry) => entry[2] === periodText)?.[0];
651
+ const implicit = units.find((entry) => entry.unit === implicitUnit);
652
+ const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
463
653
  if (Option.isSome(period)) {
464
654
  const isEnd = textAt(edge.value, 1) === "koniec";
465
- const canonical = `${isEnd ? "koniec" : "początek"} ${period.value.canonical}`;
655
+ const canonical = `${isEnd ? "koniec" : "początek"} ${withPeriodCase(period.value.canonical, "genitive")}`;
466
656
  return Option.some(isEnd ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
467
657
  }
468
658
  }
469
659
  const wrapper = [
470
660
  "w ",
661
+ "we ",
471
662
  "przez ",
472
663
  "cały ",
473
664
  "całe "
474
665
  ].find((prefix) => input.startsWith(prefix));
475
- return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
666
+ const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
667
+ return Option.isSome(base) ? base : parseCalendarOffset(input);
476
668
  };
477
669
  const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
478
670
  const countedUnitPattern = "dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat";
479
671
  const countedPattern = (source) => new RegExp(source.replace("UNIT", countedUnitPattern), "u");
480
- const calendarPastPattern = countedPattern("^([1-9]\\d*) (UNIT) temu$");
481
- const calendarFuturePattern = countedPattern("^za ([1-9]\\d*) (UNIT)$");
482
- const rollingPastPatterns = [countedPattern("^(?:ostatni|ostatnie|ostatnich|miniony|minione|minionych) ([1-9]\\d*) (UNIT)$")];
483
- const rollingFuturePatterns = [countedPattern("^(?:następny|następne|następnych|kolejny|kolejne|kolejnych) ([1-9]\\d*) (UNIT)$")];
672
+ const calendarPastPattern = countedPattern("^(?:dokładnie )?(?:([1-9]\\d*) )?(UNIT) temu$");
673
+ const calendarFuturePattern = countedPattern("^za (?:([1-9]\\d*) )?(UNIT)$");
674
+ const rollingPastPatterns = [countedPattern("^(?:ostatni|ostatnie|ostatnich|miniony|minione|minionych) ([1-9]\\d*) (UNIT)$"), countedPattern("^w ciągu (?:ostatniego|ostatnich|minionego|minionych) ([1-9]\\d*) (UNIT)$")];
675
+ const rollingFuturePatterns = [countedPattern("^(?:następny|następne|następnych|kolejny|kolejne|kolejnych) ([1-9]\\d*) (UNIT)$"), countedPattern("^w ciągu (?:najbliższego|najbliższych) ([1-9]\\d*) (UNIT)$")];
484
676
  const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
485
677
  const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
486
678
  const countNoun = (amount, entry) => {
@@ -494,15 +686,17 @@ const parseCalendarOffset = (input) => {
494
686
  const future = String$1.match(calendarFuturePattern)(input);
495
687
  const match = Option.firstSomeOf([past, future]);
496
688
  if (Option.isNone(match)) return Option.none();
497
- const amount = parseTrailingCount(textAt(match.value, 1));
498
- const unit = countedUnit(textAt(match.value, 2));
689
+ const amountText = textAt(match.value, 1);
690
+ const unitText = textAt(match.value, 2);
691
+ const amount = parseTrailingCount(amountText || "1");
692
+ const unit = countedUnit(unitText);
499
693
  if (Option.isNone(amount) || unit === void 0) return Option.none();
500
694
  const entry = units.find((item) => item.unit === unit);
501
- if (entry === void 0) return Option.none();
695
+ if (entry === void 0 || amountText.length === 0 && unitText !== entry.singular) return Option.none();
502
696
  const direction = Option.isSome(past) ? -amount.value : amount.value;
503
697
  const noun = countNoun(amount.value, entry);
504
698
  const canonical = direction < 0 ? `${amount.value} ${noun} temu` : `za ${amount.value} ${noun}`;
505
- return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
699
+ return Option.some(relativePeriod(unit, direction, canonical));
506
700
  };
507
701
  const rollingModifier = (amount, future) => {
508
702
  if (amount === 1) return future ? "następny" : "ostatni";
@@ -531,17 +725,48 @@ const parseRollingPeriod = (input) => {
531
725
  const modifier = rollingModifier(amount.value, isFuture);
532
726
  return Option.some(candidate(range, `${modifier} ${amount.value} ${countNoun(amount.value, entry)}`));
533
727
  };
728
+ const boundaries = [
729
+ [
730
+ "do początku ",
731
+ "before",
732
+ (period) => `przed ${withPeriodCase(period, "instrumental")}`
733
+ ],
734
+ [
735
+ "począwszy od ",
736
+ "since",
737
+ (period) => `od ${withPeriodCase(period, "genitive")}`
738
+ ],
739
+ [
740
+ "od ",
741
+ "since",
742
+ (period) => `od ${withPeriodCase(period, "genitive")}`
743
+ ],
744
+ [
745
+ "przed ",
746
+ "before",
747
+ (period) => `przed ${withPeriodCase(period, "instrumental")}`
748
+ ],
749
+ [
750
+ "do ",
751
+ "through",
752
+ (period) => `do ${withPeriodCase(period, "genitive")} włącznie`
753
+ ],
754
+ [
755
+ "po ",
756
+ "after",
757
+ (period) => `po ${withPeriodCase(period, "locative")}`
758
+ ]
759
+ ];
534
760
  const boundaryCandidate = (input) => {
761
+ if (input.startsWith("do koniec ")) return Option.none();
535
762
  const included = String$1.match(/^do (.+) włącznie$/u)(input);
536
- if (Option.isSome(included)) return openBoundaryCandidate(`do ${textAt(included.value, 1)}`, [["do ", "through"]], parsePeriod);
537
- return openBoundaryCandidate(input, [
538
- ["do początku ", "before"],
539
- ["począwszy od ", "since"],
540
- ["od ", "since"],
541
- ["przed ", "before"],
542
- ["do ", "through"],
543
- ["po ", "after"]
544
- ], parsePeriod);
763
+ if (Option.isSome(included)) return parsePeriod(textAt(included.value, 1)).pipe(Option.map((value) => periodBoundaryCandidate(value, "through", `do ${withPeriodCase(value.canonical, "genitive")} włącznie`)));
764
+ for (const [prefix, boundary, canonical] of boundaries) {
765
+ if (!input.startsWith(prefix)) continue;
766
+ const period = parsePeriod(input.slice(prefix.length));
767
+ if (Option.isSome(period)) return Option.some(periodBoundaryCandidate(period.value, boundary, canonical(period.value.canonical)));
768
+ }
769
+ return Option.none();
545
770
  };
546
771
  const parsePolish = (input) => {
547
772
  const remaining = remainingPhrases.find((entry) => entry.phrase === input);
@@ -552,17 +777,36 @@ const parsePolish = (input) => {
552
777
  "do teraz"
553
778
  ].includes(input)) return Option.some(candidate(untilNowRange(), "do dziś"));
554
779
  if (["od teraz", "od dziś"].includes(input)) return Option.some(candidate(fromNowRange(), "od teraz"));
555
- const offset = parseCalendarOffset(input);
556
- if (Option.isSome(offset)) return offset;
557
780
  const rolling = parseRollingPeriod(input);
558
781
  if (Option.isSome(rolling)) return rolling;
559
782
  const toDate = toDatePhrases.find((entry) => entry.phrase === input);
560
783
  if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
561
- const nowBounded = joinedNowCandidate(input, [["od ", " do dziś"], ["między ", " a dziś"]], ["od dziś do ", "między dziś a "], parsePeriod, (period) => `od ${period} do dziś`, (period) => `od dziś do ${period}`);
784
+ const elidedDayRange = String$1.match(/^(?:(?:między|pomiędzy) ([0-3]?\d)\.? a |([0-3]?\d)\.?[–—])([0-3]?\d)\.? ([a-ząćęłńóśźż]+\.?)(?: (\d{4}))?$/u)(input);
785
+ if (Option.isSome(elidedDayRange)) {
786
+ const lowerDay = textAt(elidedDayRange.value, 1) || textAt(elidedDayRange.value, 2);
787
+ const upperDay = textAt(elidedDayRange.value, 3);
788
+ const month = textAt(elidedDayRange.value, 4);
789
+ const year = textAt(elidedDayRange.value, 5);
790
+ const suffix = year.length === 0 ? "" : ` ${year}`;
791
+ const expanded = `między ${lowerDay} ${month}${suffix} a ${upperDay} ${month}${suffix}`;
792
+ const joined = joinedPeriodCandidate(expanded, [["między ", " a "], ["pomiędzy ", " a "]], parsePeriod, (lower, upper) => `od ${lower} do ${upper} włącznie`);
793
+ if (Option.isSome(joined)) return joined;
794
+ }
795
+ const nowBounded = joinedNowCandidate(input, [
796
+ ["od ", " do dziś"],
797
+ ["między ", " a dziś"],
798
+ ["pomiędzy ", " a dziś"]
799
+ ], [
800
+ "od dziś do ",
801
+ "między dziś a ",
802
+ "pomiędzy dziś a ",
803
+ "teraz do "
804
+ ], parsePeriod, (period) => `od ${period} do dziś`, (period) => `od dziś do ${period}`);
562
805
  if (Option.isSome(nowBounded)) return nowBounded;
563
806
  const bounded = joinedPeriodCandidate(input, [
564
807
  ["od ", " do "],
565
808
  ["między ", " a "],
809
+ ["pomiędzy ", " a "],
566
810
  ["", " - "],
567
811
  ["", " – "],
568
812
  ["", " — "]
@@ -570,16 +814,33 @@ const parsePolish = (input) => {
570
814
  if (Option.isSome(bounded)) return bounded;
571
815
  const boundary = boundaryCandidate(input);
572
816
  if (Option.isSome(boundary)) return boundary;
573
- return Option.map(parsePeriod(input), (period) => candidate(periodRange(period), period.canonical));
817
+ return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
574
818
  };
575
- const staticPeriodPhrases = [
576
- ...periodAliases.map((entry) => entry[0]),
819
+ const edgePhrases = [...declinedPeriods.map((entry) => entry[3]), ...monthGenitives].flatMap((phrase) => [`początek ${phrase}`, `koniec ${phrase}`]);
820
+ const canonicalPeriodPhrases = [
821
+ ...units.flatMap((entry) => [
822
+ entry.current,
823
+ entry.previous,
824
+ entry.next
825
+ ]),
826
+ "przedwczoraj",
827
+ "pojutrze",
828
+ "przedostatni tydzień",
829
+ "tydzień po następnym",
830
+ "przedostatni miesiąc",
831
+ "miesiąc po następnym",
832
+ "przedostatni rok",
833
+ "rok po następnym",
577
834
  "ten weekend",
578
835
  "poprzedni weekend",
579
836
  "następny weekend",
580
837
  "weekend przed poprzednim",
581
- "weekend po następnym",
582
- ...periodAliases.flatMap((entry) => [`początek ${entry[0]}`, `koniec ${entry[0]}`]),
838
+ "weekend po następnym"
839
+ ];
840
+ const staticPeriodPhrases = [
841
+ ...periodAliases.map((entry) => entry[0]),
842
+ ...canonicalPeriodPhrases,
843
+ ...edgePhrases,
583
844
  ...[
584
845
  1,
585
846
  2,
@@ -593,18 +854,32 @@ const staticPeriodPhrases = [
593
854
  ]),
594
855
  ...months.flatMap((month) => [
595
856
  month,
857
+ `poprzedni ${month}`,
858
+ `następny ${month}`,
596
859
  `${month} poprzedniego roku`,
597
860
  `${month} następnego roku`
598
- ])
861
+ ]),
862
+ ...nextWeekdays.map((entry) => entry.phrase)
599
863
  ];
600
864
  const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
601
- const boundaryPrefixes = [
602
- "od ",
603
- "począwszy od ",
604
- "przed ",
605
- "do ",
606
- "po "
607
- ];
865
+ const monthBoundaryPhrases = months.flatMap((_, index) => {
866
+ const genitive = textAt(monthGenitives, index);
867
+ const instrumental = textAt(monthInstrumentals, index);
868
+ const locative = textAt(monthLocatives, index);
869
+ return [
870
+ `od ${genitive}`,
871
+ `począwszy od ${genitive}`,
872
+ `przed ${instrumental}`,
873
+ `do ${genitive} włącznie`,
874
+ `po ${locative}`
875
+ ];
876
+ });
877
+ const fixedBoundaryPhrases = (phrases) => phrases.flatMap((phrase) => [
878
+ `od ${withMonthCase(phrase, monthGenitives)}`,
879
+ `przed ${withMonthCase(phrase, monthInstrumentals)}`,
880
+ `do ${withMonthCase(phrase, monthGenitives)} włącznie`,
881
+ `po ${withMonthCase(phrase, monthLocatives)}`
882
+ ]);
608
883
  const countedSuggestions = (input) => {
609
884
  const amount = naturalCount(input);
610
885
  if (amount === void 0) return [];
@@ -620,23 +895,47 @@ const countedSuggestions = (input) => {
620
895
  });
621
896
  };
622
897
  const polishSuggestionPhrases = [
623
- ...units.map((entry) => entry.toDate),
624
- ...units.map((entry) => entry.remaining),
625
- ...staticPeriodPhrases,
626
- ...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
898
+ ...toDatePhrases.map((entry) => entry.phrase),
899
+ ...remainingPhrases.map((entry) => entry.phrase),
900
+ ...canonicalPeriodPhrases,
901
+ ...edgePhrases,
902
+ ...[
903
+ 1,
904
+ 2,
905
+ 3,
906
+ 4
907
+ ].flatMap((quarter) => [
908
+ `q${quarter}`,
909
+ `q${quarter} tego roku`,
910
+ `q${quarter} poprzedniego roku`,
911
+ `q${quarter} następnego roku`
912
+ ]),
913
+ ...months,
914
+ ...monthBoundaryPhrases,
627
915
  "do dziś",
628
916
  "od teraz"
629
917
  ];
630
918
  const suggestPolish = (input, limit) => {
631
- const fixed = fixedCalendarPeriodPhrases(input, months);
632
- return completeNaturalPhrases(input, [
919
+ const correctedInput = input.startsWith("do koniec") ? `do końca${input.slice(9)}` : input;
920
+ const fixed = fixedCalendarPeriodPhrases(correctedInput, months);
921
+ return completeNaturalPhrases(correctedInput, [
633
922
  ...polishSuggestionPhrases,
634
923
  ...fixed,
635
- ...prefixNaturalPhrases(fixed, boundaryPrefixes),
636
- ...countedSuggestions(input)
924
+ ...fixedBoundaryPhrases(fixed),
925
+ ...countedSuggestions(correctedInput)
637
926
  ], limit);
638
927
  };
639
928
  const renderPolish = (range) => {
929
+ const shifted = decomposeShiftedPeriodRange(range);
930
+ if (Option.isSome(shifted)) {
931
+ const base = renderPolish(shifted.value.baseRange);
932
+ const entry = units.find((unit) => unit.unit === shifted.value.unit);
933
+ if (Option.isSome(base) && entry !== void 0) {
934
+ const amount = Math.abs(shifted.value.amount);
935
+ const noun = countNoun(amount, entry);
936
+ return Option.some(shifted.value.amount < 0 ? `${base.value} ${amount} ${noun} temu` : `${base.value} za ${amount} ${noun}`);
937
+ }
938
+ }
640
939
  const offset = calendarPeriodOffset(range);
641
940
  if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
642
941
  const entry = units.find((unit) => unit.unit === offset.value.unit);
@@ -656,13 +955,19 @@ const renderPolish = (range) => {
656
955
  const entry = units.find((unit) => unit.unit === trailing.value.unit);
657
956
  if (entry !== void 0) return Option.some(`${rollingModifier(trailing.value.amount, false)} ${trailing.value.amount} ${countNoun(trailing.value.amount, entry)}`);
658
957
  }
659
- const periods = [...staticPeriods, ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)];
660
- return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `od ${withMonthCase(period, monthGenitives)}`, (period) => `przed ${withMonthCase(period, monthInstrumentals)}`, (period) => `do ${withMonthCase(period, monthGenitives)} włącznie`, (period) => `po ${withMonthCase(period, monthLocatives)}`, (lower, upper) => `od ${withMonthCase(lower, monthGenitives)} do ${withMonthCase(upper, monthGenitives)} włącznie`, (period) => `od ${withMonthCase(period, monthGenitives)} do dziś`, (period) => `od dziś do ${withMonthCase(period, monthGenitives)}`, () => "do dziś", () => "od teraz");
958
+ const periods = [
959
+ ...staticPeriods,
960
+ ...currentYearDatePeriods(range, currentDateLabel),
961
+ ...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)
962
+ ];
963
+ return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `od ${withPeriodCase(period, "genitive")}`, (period) => `przed ${withPeriodCase(period, "instrumental")}`, (period) => `do ${withPeriodCase(period, "genitive")} włącznie`, (period) => `po ${withPeriodCase(period, "locative")}`, (lower, upper) => `od ${withPeriodCase(lower, "genitive")} do ${withPeriodCase(upper, "genitive")} włącznie`, (period) => `od ${withPeriodCase(period, "genitive")} do dziś`, (period) => `od dziś do ${withPeriodCase(period, "genitive")}`, () => "do dziś", () => "od teraz");
661
964
  };
662
965
  const PolishContribution = new BaseLanguageContribution({
663
966
  locale: "pl",
664
967
  vocabulary: [
665
968
  ...months,
969
+ ...weekdays,
970
+ ...weekdayGenitives,
666
971
  ...monthGenitives,
667
972
  ...monthInstrumentals,
668
973
  ...monthLocatives,
@@ -677,27 +982,34 @@ const PolishContribution = new BaseLanguageContribution({
677
982
  ]),
678
983
  ...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
679
984
  ...remainingPhrases.flatMap((entry) => entry.phrase.split(" ")),
985
+ ...periodAliases.flatMap((entry) => entry[0].split(" ")),
986
+ "ciągu",
987
+ "dokładnie",
680
988
  "dziś",
681
989
  "dotychczas",
682
990
  "koniec",
991
+ "końca",
683
992
  "kolejne",
684
993
  "między",
685
994
  "minione",
995
+ "najbliższych",
686
996
  "następne",
687
997
  "od",
688
998
  "ostatnie",
689
999
  "po",
690
1000
  "początek",
691
1001
  "począwszy",
1002
+ "pomiędzy",
692
1003
  "poprzedni",
693
1004
  "przed",
694
1005
  "teraz",
695
1006
  "temu",
696
1007
  "włącznie",
1008
+ "we",
697
1009
  "za"
698
1010
  ],
699
- normalize: normalizeNaturalText,
700
- correct: correctWhitespaceSeparatedText,
1011
+ normalize: normalizePolish,
1012
+ correct: correctPolish,
701
1013
  parseExact: parsePolish,
702
1014
  suggest: suggestPolish,
703
1015
  render: renderPolish