chronolizer 0.3.0 → 0.4.1

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.
@@ -1,10 +1,9 @@
1
- import { isIsoDate } from "../ast/schemas.mjs";
2
1
  import { BaseLanguageContribution } from "../language/model.mjs";
3
2
  import { normalizeNaturalText } from "../natural/text.mjs";
4
3
  import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
5
4
  import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
6
5
  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, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekend, remainingPeriodRange, renderPeriodRange, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
6
+ import { absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, compoundCountAliases, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decimalTens, decomposeShiftedPeriodRange, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodEndDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
8
7
  import { Effect, Option, String as String$1 } from "effect";
9
8
  //#region src/locales/cs.ts
10
9
  const months = [
@@ -21,6 +20,123 @@ const months = [
21
20
  "listopad",
22
21
  "prosinec"
23
22
  ];
23
+ const weekdays = [
24
+ "pondělí",
25
+ "úterý",
26
+ "středa",
27
+ "čtvrtek",
28
+ "pátek",
29
+ "sobota",
30
+ "neděle"
31
+ ];
32
+ const weekdayGenitives = [
33
+ "pondělí",
34
+ "úterý",
35
+ "středy",
36
+ "čtvrtka",
37
+ "pátku",
38
+ "soboty",
39
+ "neděle"
40
+ ];
41
+ const nextWeekdays = weekdays.flatMap((weekday, day) => [{
42
+ phrase: `příští ${weekday}`,
43
+ day,
44
+ canonical: `příští ${weekday}`
45
+ }, {
46
+ phrase: `příštího ${textAt(weekdayGenitives, day)}`,
47
+ day,
48
+ canonical: `příští ${weekday}`
49
+ }]);
50
+ const czechCountWords = [
51
+ [
52
+ "dva",
53
+ "dvě",
54
+ "dvou",
55
+ "dvěma"
56
+ ],
57
+ [
58
+ "tři",
59
+ "tří",
60
+ "třemi"
61
+ ],
62
+ [
63
+ "čtyři",
64
+ "čtyř",
65
+ "čtyřmi"
66
+ ],
67
+ ["pět", "pěti"],
68
+ ["šest", "šesti"],
69
+ ["sedm", "sedmi"],
70
+ ["osm", "osmi"],
71
+ ["devět", "devíti"],
72
+ ["deset", "deseti"],
73
+ ["jedenáct", "jedenácti"],
74
+ ["dvanáct", "dvanácti"],
75
+ ["třináct", "třinácti"],
76
+ ["čtrnáct", "čtrnácti"],
77
+ ["patnáct", "patnácti"],
78
+ ["šestnáct", "šestnácti"],
79
+ ["sedmnáct", "sedmnácti"],
80
+ ["osmnáct", "osmnácti"],
81
+ ["devatenáct", "devatenácti"],
82
+ ["dvacet", "dvaceti"]
83
+ ];
84
+ const czechCountOnes = [
85
+ [1, "jedna"],
86
+ [2, "dva"],
87
+ [3, "tři"],
88
+ [4, "čtyři"],
89
+ [5, "pět"],
90
+ [6, "šest"],
91
+ [7, "sedm"],
92
+ [8, "osm"],
93
+ [9, "devět"]
94
+ ];
95
+ const czechObliqueCountOnes = [
96
+ [1, "jedním"],
97
+ [2, "dvěma"],
98
+ [3, "třemi"],
99
+ [4, "čtyřmi"],
100
+ [5, "pěti"],
101
+ [6, "šesti"],
102
+ [7, "sedmi"],
103
+ [8, "osmi"],
104
+ [9, "devíti"]
105
+ ];
106
+ const czechCountAliases = [
107
+ ...sequentialCountAliases([[
108
+ "jeden",
109
+ "jedna",
110
+ "jedno",
111
+ "jedním",
112
+ "jednou"
113
+ ]], 1),
114
+ ...sequentialCountAliases(czechCountWords, 2),
115
+ ...compoundCountAliases(decimalTens([
116
+ "dvacet",
117
+ "třicet",
118
+ "čtyřicet",
119
+ "padesát",
120
+ "šedesát",
121
+ "sedmdesát",
122
+ "osmdesát",
123
+ "devadesát"
124
+ ]), czechCountOnes, (ten, one) => [`${ten} ${one}`]),
125
+ ...compoundCountAliases(decimalTens([
126
+ "dvaceti",
127
+ "třiceti",
128
+ "čtyřiceti",
129
+ "padesáti",
130
+ "šedesáti",
131
+ "sedmdesáti",
132
+ "osmdesáti",
133
+ "devadesáti"
134
+ ]), czechObliqueCountOnes, (ten, one) => [`${ten} ${one}`])
135
+ ];
136
+ const normalizeCzechCounts = compileCountAliasNormalizer(czechCountAliases);
137
+ const czechCountVocabulary = new Set(countAliasVocabulary(czechCountAliases));
138
+ const correctCzech = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, czechCountVocabulary);
139
+ const normalizeCzech = (input, locale) => normalizeCzechCounts(normalizeNaturalText(input, locale));
24
140
  const monthGenitives = [
25
141
  "ledna",
26
142
  "února",
@@ -369,15 +485,7 @@ const parseNamedDate = (input) => {
369
485
  const named = String$1.match(/^([0-3]?\d)\.?(?: )([a-záčďéěíňóřšťúůýž]+\.?) (\d{4})$/u)(input);
370
486
  if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
371
487
  const numeric = String$1.match(/^([0-3]?\d)(?:\. ?|[/-])([01]?\d)(?:\. ?|[/-])(\d{4})$/u)(input);
372
- if (Option.isSome(numeric)) {
373
- const year = validYear(textAt(numeric.value, 3));
374
- const month = Number(textAt(numeric.value, 2));
375
- const day = Number(textAt(numeric.value, 1));
376
- if (year !== void 0 && month >= 1 && month <= 12) {
377
- const value = isoDate(year, month, day);
378
- if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
379
- }
380
- }
488
+ if (Option.isSome(numeric)) return namedDatePeriod(textAt(numeric.value, 3), textAt(numeric.value, 2), textAt(numeric.value, 1), Number, dateLabel);
381
489
  const current = String$1.match(/^([0-3]?\d)\.? ([a-záčďéěíňóřšťúůýž]+\.?)$/u)(input);
382
490
  return Option.isSome(current) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : Option.none();
383
491
  };
@@ -406,13 +514,13 @@ const parseQuarter = (input) => {
406
514
  const quarter = quarterNumber(textAt(standalone.value, 1));
407
515
  return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
408
516
  };
409
- const parseBasePeriod = (input) => {
410
- const absoluteDate = absoluteDatePeriod(input, "cs");
411
- if (Option.isSome(absoluteDate)) return absoluteDate;
412
- const namedDate = parseNamedDate(input);
413
- if (Option.isSome(namedDate)) return namedDate;
414
- const quarter = parseQuarter(input);
415
- if (Option.isSome(quarter)) return quarter;
517
+ const parseDatedPeriod = (input) => {
518
+ const knownPeriod = Option.firstSomeOf([
519
+ absoluteDatePeriod(input, "cs"),
520
+ parseNamedDate(input),
521
+ parseQuarter(input)
522
+ ]);
523
+ if (Option.isSome(knownPeriod)) return knownPeriod;
416
524
  const yearMatch = String$1.match(/^(?:rok |roku )?(\d{4})$/u)(input);
417
525
  if (Option.isSome(yearMatch)) {
418
526
  const year = validYear(textAt(yearMatch.value, 1));
@@ -424,6 +532,20 @@ const parseBasePeriod = (input) => {
424
532
  const year = validYear(textAt(monthYear.value, 2));
425
533
  if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
426
534
  }
535
+ return Option.none();
536
+ };
537
+ const parseBasePeriod = (input) => {
538
+ const datedPeriod = parseDatedPeriod(input);
539
+ if (Option.isSome(datedPeriod)) return datedPeriod;
540
+ const prefixedRelativeMonth = String$1.match(/^(minulý|tento|příští) ([a-záčďéěíňóřšťúůýž]+\.?)$/u)(input);
541
+ if (Option.isSome(prefixedRelativeMonth)) {
542
+ const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
543
+ if (month !== void 0) {
544
+ const modifier = textAt(prefixedRelativeMonth.value, 1);
545
+ const direction = relativeYearDirection(modifier);
546
+ return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${textAt(months, month - 1)}`));
547
+ }
548
+ }
427
549
  const relativeMonth = String$1.match(/^([a-záčďéěíňóřšťúůýž]+\.?) (minulého roku|příštího roku|tohoto roku)$/u)(input);
428
550
  const relativeMonthYearFirst = String$1.match(/^(minulého roku|příštího roku|tohoto roku) ([a-záčďéěíňóřšťúůýž]+\.?)$/u)(input);
429
551
  const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
@@ -444,12 +566,31 @@ const parseBasePeriod = (input) => {
444
566
  if (["příští víkend", "následující víkend"].includes(input)) return Option.some(relativeWeekend(1, "příští víkend"));
445
567
  if (input === "předminulý víkend") return Option.some(relativeWeekend(-2, input));
446
568
  if (input === "přespříští víkend") return Option.some(relativeWeekend(2, input));
447
- return Option.none();
569
+ const weekday = nextWeekdays.find((entry) => entry.phrase === input);
570
+ return weekday === void 0 ? Option.none() : Option.some(relativeWeekday(weekday.day, 1, weekday.canonical));
448
571
  };
449
572
  const parsePeriod = (input) => {
573
+ const shifted = String$1.match(/^(.+) (před|za) ([1-9]\d*) (den|dny|dnů|dnem|týden|týdny|týdnů|týdnem|měsíc|měsíce|měsíců|měsícem|měsíci|čtvrtletí|čtvrtletím|čtvrtletími|rok|roky|let|rokem|lety)$/u)(input);
574
+ if (Option.isSome(shifted)) {
575
+ const amount = parseTrailingCount(textAt(shifted.value, 3));
576
+ const alias = unitAliases.find((unit) => unit[0] === textAt(shifted.value, 4));
577
+ const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
578
+ const period = parsePeriod(textAt(shifted.value, 1));
579
+ if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
580
+ const past = textAt(shifted.value, 2) === "před";
581
+ const direction = past ? -amount.value : amount.value;
582
+ const pastNoun = amount.value === 1 ? entry.pastSingular : entry.pastPlural;
583
+ const noun = past ? pastNoun : countNoun(amount.value, entry);
584
+ const canonical = `${period.value.canonical} ${past ? "před" : "za"} ${amount.value} ${noun}`;
585
+ return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
586
+ }
587
+ }
450
588
  const edge = String$1.match(/^(začátek|počátek|konec) (.+)$/u)(input);
451
589
  if (Option.isSome(edge)) {
452
- const period = parseBasePeriod(textAt(edge.value, 2));
590
+ const periodText = textAt(edge.value, 2);
591
+ const basePeriod = parseBasePeriod(periodText);
592
+ const implicit = units.find((entry) => entry.durationGenitive === periodText);
593
+ const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
453
594
  if (Option.isSome(period)) {
454
595
  const isEnd = textAt(edge.value, 1) === "konec";
455
596
  const canonical = `${isEnd ? "konec" : "začátek"} ${period.value.canonical}`;
@@ -462,17 +603,16 @@ const parsePeriod = (input) => {
462
603
  "celý ",
463
604
  "celé "
464
605
  ].find((prefix) => input.startsWith(prefix));
465
- return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
606
+ const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
607
+ return Option.isSome(base) ? base : parseCalendarOffset(input);
466
608
  };
467
609
  const countedUnit = (value) => unitAliases.find((entry) => entry[0] === value)?.[1];
468
- const countedUnitPattern = "den|dny|dnů|dnem|týden|týdny|týdnů|týdnem|měsíc|měsíce|měsíců|měsícem|měsíci|čtvrtletí|čtvrtletím|čtvrtletími|rok|roky|let|rokem|lety";
469
- const countedPattern = (source) => new RegExp(source.replace("UNIT", countedUnitPattern), "u");
470
- const calendarPastPattern = countedPattern("^před ([1-9]\\d*) (UNIT)$");
471
- const calendarFuturePatterns = [countedPattern("^za ([1-9]\\d*) (UNIT)$")];
472
- const rollingPastPatterns = [countedPattern("^(?:poslední|posledních|uplynulé|uplynulých) ([1-9]\\d*) (UNIT)$")];
473
- const rollingFuturePatterns = [countedPattern("^(?:příští|následující) ([1-9]\\d*) (UNIT)$")];
474
- const rollingSincePattern = countedPattern("^za poslední ([1-9]\\d*) (UNIT)$");
475
- const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
610
+ const calendarPastPattern = /^před ([1-9]\d*) ([^ ]+)$/u;
611
+ const calendarFuturePatterns = [/^za ([1-9]\d*) ([^ ]+)$/u];
612
+ const rollingPastPatterns = [/^(?:poslední|posledních|uplynulé|uplynulých) ([1-9]\d*) ([^ ]+)$/u];
613
+ const rollingFuturePatterns = [/^(?:příští|následující) ([1-9]\d*) ([^ ]+)$/u];
614
+ const rollingSincePattern = /^za poslední ([1-9]\d*) ([^ ]+)$/u;
615
+ const rollingBarePattern = /^([1-9]\d*) ([^ ]+)$/u;
476
616
  const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
477
617
  const singularRollingPhrases = units.flatMap((entry) => [
478
618
  {
@@ -512,7 +652,7 @@ const parseCalendarOffset = (input) => {
512
652
  const futureNoun = countNoun(amount.value, entry);
513
653
  const pastNoun = amount.value === 1 ? entry.pastSingular : entry.pastPlural;
514
654
  const canonical = direction < 0 ? `před ${amount.value} ${pastNoun}` : `za ${amount.value} ${futureNoun}`;
515
- return Option.some(candidate(periodRange(relativePeriod(unit, direction, canonical)), canonical));
655
+ return Option.some(relativePeriod(unit, direction, canonical));
516
656
  };
517
657
  const parseRollingPeriod = (input) => {
518
658
  const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
@@ -579,17 +719,19 @@ const parseCzech = (input) => {
579
719
  "ode dneška",
580
720
  "od teď"
581
721
  ].includes(input)) return Option.some(candidate(fromNowRange(), "od nynějška"));
582
- const offset = parseCalendarOffset(input);
583
- if (Option.isSome(offset)) return offset;
584
722
  const rolling = parseRollingPeriod(input);
585
723
  if (Option.isSome(rolling)) return rolling;
586
724
  const toDate = toDatePhrases.find((entry) => entry.phrase === input);
587
725
  if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry.unit), toDate.entry.toDate));
588
726
  const elided = parseElidedDateRange(input);
589
727
  if (Option.isSome(elided)) return elided;
590
- const nowBounded = joinedNowCandidate(input, [["od ", " do dneška"], ["mezi ", " a dneškem"]], ["od dneška do ", "mezi dneškem a "], parsePeriod, (period) => `od ${period} do dneška`, (period) => `od dneška do ${period}`);
728
+ const nowBounded = joinedNowCandidate(input, [["od ", " do dneška"], ["mezi ", " a dneškem"]], [
729
+ "od dneška do ",
730
+ "mezi dneškem a ",
731
+ "dnešek až "
732
+ ], parsePeriod, (period) => `od ${period} do dneška`, (period) => `od dneška do ${period}`);
591
733
  if (Option.isSome(nowBounded)) return nowBounded;
592
- const bounded = joinedPeriodCandidate(input, [
734
+ const bounded = joinedPeriodCandidate(input.replace(/ včetně$/u, ""), [
593
735
  ["od ", " do "],
594
736
  ["mezi ", " a "],
595
737
  ["", " - "],
@@ -609,6 +751,8 @@ const staticPeriodPhrases = [
609
751
  "předminulý víkend",
610
752
  "přespříští víkend",
611
753
  ...periodAliases.flatMap((entry) => [`začátek ${entry[0]}`, `konec ${entry[0]}`]),
754
+ ...nextWeekdays.map((entry) => entry.phrase),
755
+ ...months.flatMap((month) => [`minulý ${month}`, `příští ${month}`]),
612
756
  ...[
613
757
  1,
614
758
  2,
@@ -670,6 +814,18 @@ const suggestCzech = (input, limit) => {
670
814
  ], limit);
671
815
  };
672
816
  const renderCzech = (range) => {
817
+ const shifted = decomposeShiftedPeriodRange(range);
818
+ if (Option.isSome(shifted)) {
819
+ const base = renderCzech(shifted.value.baseRange);
820
+ const entry = units.find((unit) => unit.unit === shifted.value.unit);
821
+ if (Option.isSome(base) && entry !== void 0) {
822
+ const amount = Math.abs(shifted.value.amount);
823
+ const pastNoun = amount === 1 ? entry.pastSingular : entry.pastPlural;
824
+ const noun = shifted.value.amount < 0 ? pastNoun : countNoun(amount, entry);
825
+ const direction = shifted.value.amount < 0 ? "před" : "za";
826
+ return Option.some(`${base.value} ${direction} ${amount} ${noun}`);
827
+ }
828
+ }
673
829
  const offset = calendarPeriodOffset(range);
674
830
  if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
675
831
  const entry = units.find((unit) => unit.unit === offset.value.unit);
@@ -701,6 +857,8 @@ const CzechContribution = new BaseLanguageContribution({
701
857
  locale: "cs",
702
858
  vocabulary: [
703
859
  ...months,
860
+ ...weekdays,
861
+ ...weekdayGenitives,
704
862
  ...monthAbbreviations.flatMap((aliases) => aliases),
705
863
  ...units.flatMap((entry) => [
706
864
  entry.singular,
@@ -731,8 +889,8 @@ const CzechContribution = new BaseLanguageContribution({
731
889
  "začátek",
732
890
  "zbytek"
733
891
  ],
734
- normalize: normalizeNaturalText,
735
- correct: correctWhitespaceSeparatedText,
892
+ normalize: normalizeCzech,
893
+ correct: correctCzech,
736
894
  parseExact: parseCzech,
737
895
  suggest: suggestCzech,
738
896
  render: renderCzech