chronolizer 0.3.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.
@@ -4,7 +4,7 @@ import { normalizeNaturalText } from "../natural/text.mjs";
4
4
  import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
5
5
  import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
6
6
  import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount, prefixNaturalPhrases } from "../natural/suggestion.mjs";
7
- import { absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodDay, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
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, openBoundaryCandidate, parseTrailingCount, periodDay, 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/nl.ts
10
10
  const months = [
@@ -21,6 +21,54 @@ const months = [
21
21
  "november",
22
22
  "december"
23
23
  ];
24
+ const weekdays = [
25
+ "maandag",
26
+ "dinsdag",
27
+ "woensdag",
28
+ "donderdag",
29
+ "vrijdag",
30
+ "zaterdag",
31
+ "zondag"
32
+ ];
33
+ const nextWeekdayPhrases = weekdays.map((weekday) => `volgende ${weekday}`);
34
+ const dutchCountWords = [
35
+ "twee",
36
+ "drie",
37
+ "vier",
38
+ "vijf",
39
+ "zes",
40
+ "zeven",
41
+ "acht",
42
+ "negen",
43
+ "tien",
44
+ "elf",
45
+ "twaalf",
46
+ "dertien",
47
+ "veertien",
48
+ "vijftien",
49
+ "zestien",
50
+ "zeventien",
51
+ "achttien",
52
+ "negentien",
53
+ "twintig"
54
+ ];
55
+ const dutchCountOnes = [[1, "een"], ...dutchCountWords.slice(0, 8).map((word, index) => [index + 2, word])];
56
+ const dutchCountAliases = [...sequentialCountAliases(dutchCountWords.map((word) => [word]), 2), ...compoundCountAliases(decimalTens([
57
+ "twintig",
58
+ "dertig",
59
+ "veertig",
60
+ "vijftig",
61
+ "zestig",
62
+ "zeventig",
63
+ "tachtig",
64
+ "negentig"
65
+ ]), dutchCountOnes, (ten, one) => {
66
+ return [`${one === "twee" || one === "drie" ? `${one}ën` : `${one}en`}${ten}`];
67
+ })];
68
+ const normalizeDutchCounts = compileCountAliasNormalizer(dutchCountAliases);
69
+ const dutchCountVocabulary = new Set(countAliasVocabulary(dutchCountAliases));
70
+ const correctDutch = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, dutchCountVocabulary);
71
+ const normalizeDutch = (input, locale) => normalizeDutchCounts(normalizeNaturalText(input, locale));
24
72
  const monthAbbreviations = [
25
73
  ["jan"],
26
74
  ["feb"],
@@ -395,13 +443,13 @@ const parseQuarter = (input) => {
395
443
  const quarter = quarterNumber(textAt(standalone.value, 1));
396
444
  return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `K${quarter}`));
397
445
  };
398
- const parseBasePeriod = (input) => {
399
- const absoluteDate = absoluteDatePeriod(input, "nl");
400
- if (Option.isSome(absoluteDate)) return absoluteDate;
401
- const namedDate = parseNamedDate(input);
402
- if (Option.isSome(namedDate)) return namedDate;
403
- const quarter = parseQuarter(input);
404
- if (Option.isSome(quarter)) return quarter;
446
+ const parseDatedPeriod = (input) => {
447
+ const knownPeriod = Option.firstSomeOf([
448
+ absoluteDatePeriod(input, "nl"),
449
+ parseNamedDate(input),
450
+ parseQuarter(input)
451
+ ]);
452
+ if (Option.isSome(knownPeriod)) return knownPeriod;
405
453
  const yearMatch = String$1.match(/^(?:het jaar |jaar )?(\d{4})$/u)(input);
406
454
  if (Option.isSome(yearMatch)) {
407
455
  const year = validYear(textAt(yearMatch.value, 1));
@@ -413,6 +461,20 @@ const parseBasePeriod = (input) => {
413
461
  const year = validYear(textAt(monthYear.value, 2));
414
462
  if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
415
463
  }
464
+ return Option.none();
465
+ };
466
+ const parseBasePeriod = (input) => {
467
+ const datedPeriod = parseDatedPeriod(input);
468
+ if (Option.isSome(datedPeriod)) return datedPeriod;
469
+ const prefixedRelativeMonth = String$1.match(/^(vorige|deze|volgende) ([a-z]+\.?)$/u)(input);
470
+ if (Option.isSome(prefixedRelativeMonth)) {
471
+ const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
472
+ if (month !== void 0) {
473
+ const modifier = textAt(prefixedRelativeMonth.value, 1);
474
+ const direction = relativeYearDirection(modifier);
475
+ return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${textAt(months, month - 1)}`));
476
+ }
477
+ }
416
478
  const relativeMonth = String$1.match(/^([a-z]+\.?)(?: van)? (vorig jaar|volgend jaar|dit jaar)$/u)(input);
417
479
  const relativeMonthYearFirst = String$1.match(/^(vorig jaar|volgend jaar|dit jaar) ([a-z]+\.?)$/u)(input);
418
480
  const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
@@ -445,13 +507,33 @@ const parseBasePeriod = (input) => {
445
507
  ].includes(input)) return Option.some(relativeWeekend(1, "volgend weekend"));
446
508
  if (input === "het weekend voor het vorige") return Option.some(relativeWeekend(-2, input));
447
509
  if (input === "het weekend na het volgende") return Option.some(relativeWeekend(2, input));
448
- return Option.none();
510
+ const weekday = nextWeekdayPhrases.indexOf(input);
511
+ return weekday === -1 ? Option.none() : Option.some(relativeWeekday(weekday, 1, nextWeekdayPhrases[weekday] ?? input));
449
512
  };
450
513
  const parsePeriod = (input) => {
514
+ const shifted = String$1.match(/^(.+) (?:(?:over|binnen) ([1-9]\d*) (dag|dagen|week|weken|maand|maanden|kwartaal|kwartalen|jaar|jaren)|([1-9]\d*) (dag|dagen|week|weken|maand|maanden|kwartaal|kwartalen|jaar|jaren) (geleden|later))$/u)(input);
515
+ if (Option.isSome(shifted)) {
516
+ const suffixAmount = textAt(shifted.value, 4);
517
+ const amount = parseTrailingCount(suffixAmount || textAt(shifted.value, 2));
518
+ const unitText = textAt(shifted.value, suffixAmount.length > 0 ? 5 : 3);
519
+ const alias = unitAliases.find((unit) => unit[0] === unitText);
520
+ const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
521
+ const period = parsePeriod(textAt(shifted.value, 1));
522
+ if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
523
+ const past = textAt(shifted.value, 6) === "geleden";
524
+ const direction = past ? -amount.value : amount.value;
525
+ const noun = amount.value === 1 ? entry.singular : entry.plural;
526
+ const canonical = past ? `${period.value.canonical} ${amount.value} ${noun} geleden` : `${period.value.canonical} over ${amount.value} ${noun}`;
527
+ return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
528
+ }
529
+ }
451
530
  const edge = String$1.match(/^(?:het )?(begin|eind|einde)(?: van)? (.+)$/u)(input);
452
531
  if (Option.isSome(edge)) {
453
532
  const edgeName = textAt(edge.value, 1);
454
- const period = parseBasePeriod(textAt(edge.value, 2));
533
+ const periodText = textAt(edge.value, 2);
534
+ const basePeriod = parseBasePeriod(periodText);
535
+ const implicit = units.find((entry) => `${entry.article} ${entry.singular}` === periodText);
536
+ const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
455
537
  if (Option.isSome(period)) {
456
538
  const isEnd = edgeName === "eind" || edgeName === "einde";
457
539
  const canonical = `${isEnd ? "eind" : "begin"} van ${period.value.canonical}`;
@@ -465,7 +547,8 @@ const parsePeriod = (input) => {
465
547
  "de hele ",
466
548
  "het hele "
467
549
  ].find((prefix) => input.startsWith(prefix));
468
- return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
550
+ const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
551
+ return Option.isSome(base) ? base : parseCalendarOffset(input);
469
552
  };
470
553
  const countedUnit = (value, amount) => {
471
554
  const expected = amount === 1 ? "singular" : "plural";
@@ -528,7 +611,7 @@ const singularCalendarOffsets = units.flatMap((entry) => [
528
611
  ]);
529
612
  const parseCalendarOffset = (input) => {
530
613
  const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
531
- if (singular !== void 0) return Option.some(candidate(periodRange(relativePeriod(singular.entry.unit, singular.direction, singular.phrase)), singular.phrase));
614
+ if (singular !== void 0) return Option.some(relativePeriod(singular.entry.unit, singular.direction, singular.phrase));
532
615
  const past = String$1.match(calendarPastPattern)(input);
533
616
  const future = firstPatternMatch(input, calendarFuturePatterns);
534
617
  const match = Option.firstSomeOf([past, future]);
@@ -540,7 +623,7 @@ const parseCalendarOffset = (input) => {
540
623
  const direction = Option.isSome(past) ? -amount.value : amount.value;
541
624
  const noun = amount.value === 1 ? entry.singular : entry.plural;
542
625
  const canonical = direction < 0 ? `${amount.value} ${noun} geleden` : `over ${amount.value} ${noun}`;
543
- return Option.some(candidate(periodRange(relativePeriod(entry.unit, direction, canonical)), canonical));
626
+ return Option.some(relativePeriod(entry.unit, direction, canonical));
544
627
  };
545
628
  const parseRollingPeriod = (input) => {
546
629
  const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
@@ -604,8 +687,6 @@ const parseDutch = (input) => {
604
687
  "sinds nu",
605
688
  "voortaan"
606
689
  ].includes(input)) return Option.some(candidate(fromNowRange(), "vanaf nu"));
607
- const offset = parseCalendarOffset(input);
608
- if (Option.isSome(offset)) return offset;
609
690
  const rolling = parseRollingPeriod(input);
610
691
  if (Option.isSome(rolling)) return rolling;
611
692
  const toDate = toDatePhrases.find((entry) => entry.phrase === input);
@@ -619,7 +700,8 @@ const parseDutch = (input) => {
619
700
  ], [
620
701
  "vanaf nu tot en met ",
621
702
  "van vandaag tot en met ",
622
- "tussen vandaag en "
703
+ "tussen vandaag en ",
704
+ "nu tot en met "
623
705
  ], parsePeriod, (period) => `vanaf ${period} tot nu toe`, (period) => `vanaf nu tot en met ${period}`);
624
706
  if (Option.isSome(nowBounded)) return nowBounded;
625
707
  const bounded = joinedPeriodCandidate(input, [
@@ -670,9 +752,12 @@ const staticPeriodPhrases = [
670
752
  ]),
671
753
  ...months.flatMap((month) => [
672
754
  month,
755
+ `vorige ${month}`,
756
+ `volgende ${month}`,
673
757
  `${month} vorig jaar`,
674
758
  `${month} volgend jaar`
675
- ])
759
+ ]),
760
+ ...nextWeekdayPhrases
676
761
  ];
677
762
  const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
678
763
  const boundaryPrefixes = [
@@ -719,6 +804,16 @@ const suggestDutch = (input, limit) => {
719
804
  ], limit);
720
805
  };
721
806
  const renderDutch = (range) => {
807
+ const shifted = decomposeShiftedPeriodRange(range);
808
+ if (Option.isSome(shifted)) {
809
+ const base = renderDutch(shifted.value.baseRange);
810
+ const entry = units.find((unit) => unit.unit === shifted.value.unit);
811
+ if (Option.isSome(base) && entry !== void 0) {
812
+ const amount = Math.abs(shifted.value.amount);
813
+ const noun = amount === 1 ? entry.singular : entry.plural;
814
+ return Option.some(shifted.value.amount < 0 ? `${base.value} ${amount} ${noun} geleden` : `${base.value} over ${amount} ${noun}`);
815
+ }
816
+ }
722
817
  const offset = calendarPeriodOffset(range);
723
818
  if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
724
819
  const entry = units.find((unit) => unit.unit === offset.value.unit);
@@ -749,6 +844,7 @@ const DutchContribution = new BaseLanguageContribution({
749
844
  locale: "nl",
750
845
  vocabulary: [
751
846
  ...months,
847
+ ...weekdays,
752
848
  ...monthAbbreviations.flatMap((aliases) => aliases),
753
849
  ...units.flatMap((entry) => [
754
850
  entry.singular,
@@ -763,10 +859,13 @@ const DutchContribution = new BaseLanguageContribution({
763
859
  "begin",
764
860
  "binnen",
765
861
  "eind",
862
+ "en",
766
863
  "geleden",
767
864
  "komende",
768
865
  "laatste",
866
+ "met",
769
867
  "na",
868
+ "nu",
770
869
  "over",
771
870
  "rest",
772
871
  "sinds",
@@ -776,8 +875,8 @@ const DutchContribution = new BaseLanguageContribution({
776
875
  "volgend",
777
876
  "voor"
778
877
  ],
779
- normalize: normalizeNaturalText,
780
- correct: correctWhitespaceSeparatedText,
878
+ normalize: normalizeDutch,
879
+ correct: correctDutch,
781
880
  parseExact: parseDutch,
782
881
  suggest: suggestDutch,
783
882
  render: renderDutch
@@ -4,7 +4,7 @@ import { normalizeNaturalText } from "../natural/text.mjs";
4
4
  import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
5
5
  import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
6
6
  import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount } 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, parseTrailingCount, periodBoundaryCandidate, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.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",
@@ -484,13 +573,13 @@ const parseQuarter = (input) => {
484
573
  const quarter = quarterNumber(textAt(standalone.value, 1));
485
574
  return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
486
575
  };
487
- const parseBasePeriod = (input) => {
488
- const absoluteDate = absoluteDatePeriod(input, "pl");
489
- if (Option.isSome(absoluteDate)) return absoluteDate;
490
- const namedDate = parseNamedDate(input);
491
- if (Option.isSome(namedDate)) return namedDate;
492
- const quarter = parseQuarter(input);
493
- 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;
494
583
  const yearMatch = String$1.match(/^(?:rok |roku )?(\d{4})$/u)(input);
495
584
  if (Option.isSome(yearMatch)) {
496
585
  const year = validYear(textAt(yearMatch.value, 1));
@@ -502,6 +591,20 @@ const parseBasePeriod = (input) => {
502
591
  const year = validYear(textAt(monthYear.value, 2));
503
592
  if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
504
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
+ }
505
608
  const relativeMonth = String$1.match(/^([a-ząćęłńóśźż]+\.?) (poprzedniego roku|następnego roku|tego roku)$/u)(input);
506
609
  const relativeMonthYearFirst = String$1.match(/^(poprzedniego roku|następnego roku|tego roku) ([a-ząćęłńóśźż]+\.?)$/u)(input);
507
610
  const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
@@ -522,12 +625,31 @@ const parseBasePeriod = (input) => {
522
625
  if (["następny weekend", "przyszły weekend"].includes(input)) return Option.some(relativeWeekend(1, "następny weekend"));
523
626
  if (input === "weekend przed poprzednim") return Option.some(relativeWeekend(-2, input));
524
627
  if (input === "weekend po następnym") return Option.some(relativeWeekend(2, input));
525
- 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));
526
630
  };
527
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
+ }
528
646
  const edge = String$1.match(/^(początek|koniec) (.+)$/u)(input);
529
647
  if (Option.isSome(edge)) {
530
- 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));
531
653
  if (Option.isSome(period)) {
532
654
  const isEnd = textAt(edge.value, 1) === "koniec";
533
655
  const canonical = `${isEnd ? "koniec" : "początek"} ${withPeriodCase(period.value.canonical, "genitive")}`;
@@ -541,7 +663,8 @@ const parsePeriod = (input) => {
541
663
  "cały ",
542
664
  "całe "
543
665
  ].find((prefix) => input.startsWith(prefix));
544
- 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);
545
668
  };
546
669
  const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
547
670
  const countedUnitPattern = "dzień|dni|tydzień|tygodnie|tygodni|miesiąc|miesiące|miesięcy|kwartał|kwartały|kwartałów|rok|lata|lat";
@@ -573,7 +696,7 @@ const parseCalendarOffset = (input) => {
573
696
  const direction = Option.isSome(past) ? -amount.value : amount.value;
574
697
  const noun = countNoun(amount.value, entry);
575
698
  const canonical = direction < 0 ? `${amount.value} ${noun} temu` : `za ${amount.value} ${noun}`;
576
- return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
699
+ return Option.some(relativePeriod(unit, direction, canonical));
577
700
  };
578
701
  const rollingModifier = (amount, future) => {
579
702
  if (amount === 1) return future ? "następny" : "ostatni";
@@ -635,6 +758,7 @@ const boundaries = [
635
758
  ]
636
759
  ];
637
760
  const boundaryCandidate = (input) => {
761
+ if (input.startsWith("do koniec ")) return Option.none();
638
762
  const included = String$1.match(/^do (.+) włącznie$/u)(input);
639
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`)));
640
764
  for (const [prefix, boundary, canonical] of boundaries) {
@@ -653,8 +777,6 @@ const parsePolish = (input) => {
653
777
  "do teraz"
654
778
  ].includes(input)) return Option.some(candidate(untilNowRange(), "do dziś"));
655
779
  if (["od teraz", "od dziś"].includes(input)) return Option.some(candidate(fromNowRange(), "od teraz"));
656
- const offset = parseCalendarOffset(input);
657
- if (Option.isSome(offset)) return offset;
658
780
  const rolling = parseRollingPeriod(input);
659
781
  if (Option.isSome(rolling)) return rolling;
660
782
  const toDate = toDatePhrases.find((entry) => entry.phrase === input);
@@ -677,7 +799,8 @@ const parsePolish = (input) => {
677
799
  ], [
678
800
  "od dziś do ",
679
801
  "między dziś a ",
680
- "pomiędzy dziś a "
802
+ "pomiędzy dziś a ",
803
+ "teraz do "
681
804
  ], parsePeriod, (period) => `od ${period} do dziś`, (period) => `od dziś do ${period}`);
682
805
  if (Option.isSome(nowBounded)) return nowBounded;
683
806
  const bounded = joinedPeriodCandidate(input, [
@@ -731,9 +854,12 @@ const staticPeriodPhrases = [
731
854
  ]),
732
855
  ...months.flatMap((month) => [
733
856
  month,
857
+ `poprzedni ${month}`,
858
+ `następny ${month}`,
734
859
  `${month} poprzedniego roku`,
735
860
  `${month} następnego roku`
736
- ])
861
+ ]),
862
+ ...nextWeekdays.map((entry) => entry.phrase)
737
863
  ];
738
864
  const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
739
865
  const monthBoundaryPhrases = months.flatMap((_, index) => {
@@ -800,6 +926,16 @@ const suggestPolish = (input, limit) => {
800
926
  ], limit);
801
927
  };
802
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
+ }
803
939
  const offset = calendarPeriodOffset(range);
804
940
  if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
805
941
  const entry = units.find((unit) => unit.unit === offset.value.unit);
@@ -830,6 +966,8 @@ const PolishContribution = new BaseLanguageContribution({
830
966
  locale: "pl",
831
967
  vocabulary: [
832
968
  ...months,
969
+ ...weekdays,
970
+ ...weekdayGenitives,
833
971
  ...monthGenitives,
834
972
  ...monthInstrumentals,
835
973
  ...monthLocatives,
@@ -870,8 +1008,8 @@ const PolishContribution = new BaseLanguageContribution({
870
1008
  "we",
871
1009
  "za"
872
1010
  ],
873
- normalize: normalizeNaturalText,
874
- correct: correctWhitespaceSeparatedText,
1011
+ normalize: normalizePolish,
1012
+ correct: correctPolish,
875
1013
  parseExact: parsePolish,
876
1014
  suggest: suggestPolish,
877
1015
  render: renderPolish
@@ -6,6 +6,43 @@ import { NaturalCandidate } from "../language/model.mjs";
6
6
  import { Match, Option, RegExp as RegExp$1, Schema, String as String$1 } from "effect";
7
7
  //#region src/locales/shared.ts
8
8
  const textAt = (values, index) => values[index] ?? "";
9
+ const sequentialCountAliases = (words, start) => words.flatMap((aliases, index) => aliases.map((phrase) => [phrase, start + index]));
10
+ const decimalTens = (words) => words.map((word, index) => [20 + index * 10, word]);
11
+ const compoundCountAliases = (tens, ones, join) => tens.flatMap(([tensAmount, tensWord]) => ones.flatMap(([oneAmount, oneWord]) => {
12
+ const amount = tensAmount + oneAmount;
13
+ return join(tensWord, oneWord, amount).map((phrase) => [phrase, amount]);
14
+ }));
15
+ const countAliasVocabulary = (aliases) => [...new Set(aliases.flatMap(([phrase]) => phrase.split(" ")))];
16
+ const compileCountAliasNormalizer = (aliases) => {
17
+ const distinct = /* @__PURE__ */ new Map();
18
+ for (const [phrase, amount] of aliases) if (!distinct.has(phrase)) distinct.set(phrase, amount);
19
+ const byFirstWord = /* @__PURE__ */ new Map();
20
+ for (const [phrase, amount] of distinct) {
21
+ const parts = phrase.split(" ");
22
+ const first = parts[0] ?? "";
23
+ const entries = byFirstWord.get(first) ?? [];
24
+ byFirstWord.set(first, [...entries, {
25
+ amount,
26
+ parts
27
+ }]);
28
+ }
29
+ for (const [first, entries] of byFirstWord) byFirstWord.set(first, [...entries].sort((left, right) => right.parts.length - left.parts.length));
30
+ return (input) => {
31
+ const words = input.split(" ");
32
+ const output = [];
33
+ for (let index = 0; index < words.length;) {
34
+ const alias = (byFirstWord.get(words[index] ?? "") ?? []).find((entry) => entry.parts.every((part, offset) => words[index + offset] === part));
35
+ if (alias === void 0) {
36
+ output.push(words[index] ?? "");
37
+ index += 1;
38
+ } else {
39
+ output.push(String(alias.amount));
40
+ index += alias.parts.length;
41
+ }
42
+ }
43
+ return output.join(" ");
44
+ };
45
+ };
9
46
  const Period = Schema.Struct({
10
47
  start: InstantExpr,
11
48
  end: InstantExpr,
@@ -18,6 +55,22 @@ const isNow = Schema.is(Now);
18
55
  const isShift = Schema.is(Shift);
19
56
  const isStartOf = Schema.is(StartOf);
20
57
  const periodRange = (period) => boundedRange(greaterThanOrEqual(period.start), lessThan(period.end));
58
+ const shiftPeriod = (period, amount, unit, canonical) => Period.make({
59
+ start: shift(period.start, amount, unit),
60
+ end: shift(period.end, amount, unit),
61
+ canonical
62
+ });
63
+ const decomposeShiftedPeriodRange = (range) => {
64
+ if (!isGreaterThanOrEqual(range.lower) || !isLessThan(range.upper)) return Option.none();
65
+ const start = range.lower.value;
66
+ const end = range.upper.value;
67
+ if (!isShift(start) || !isShift(end) || start.amount === 0 || start.amount !== end.amount || start.unit !== end.unit) return Option.none();
68
+ return Option.some({
69
+ amount: start.amount,
70
+ unit: start.unit,
71
+ baseRange: boundedRange(greaterThanOrEqual(start.base), lessThan(end.base))
72
+ });
73
+ };
21
74
  const periodToDateRange = (unit) => boundedRange(greaterThanOrEqual(startOf(now(), unit)), lessThanOrEqual(now()));
22
75
  const fromNowRange = () => lowerOpenRange(greaterThanOrEqual(now()));
23
76
  const untilNowRange = () => upperOpenRange(lessThanOrEqual(now()));
@@ -49,6 +102,16 @@ const periodPreviousDay = (period, canonical) => Period.make({
49
102
  end: period.start,
50
103
  canonical
51
104
  });
105
+ const relativeWeekday = (day, direction, canonical) => {
106
+ const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
107
+ const weekStart = startOf(weekBase, "week");
108
+ const start = day === 0 ? weekStart : shift(weekStart, day, "day");
109
+ return Period.make({
110
+ start,
111
+ end: shift(start, 1, "day"),
112
+ canonical
113
+ });
114
+ };
52
115
  const relativeWeekend = (direction, canonical) => {
53
116
  const weekBase = direction === 0 ? now() : shift(now(), direction, "week");
54
117
  const weekStart = startOf(weekBase, "week");
@@ -392,4 +455,4 @@ const renderPeriodRange = (range, toDate, periods, since, before, through, after
392
455
  return Option.none();
393
456
  };
394
457
  //#endregion
395
- export { Period, absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, formatAbsoluteDate, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };
458
+ export { Period, absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedDatePeriod, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, formatAbsoluteDate, fromNowRange, futurePeriod, futureRange, isoDate, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, periodEndDay, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear };