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.
- package/README.md +46 -45
- package/dist/filter/expression.mjs +24 -18
- package/dist/language/model.mjs +1 -1
- package/dist/language/registry.mjs +32 -12
- package/dist/locales/cs.mjs +186 -17
- package/dist/locales/de.mjs +133 -20
- package/dist/locales/en.mjs +151 -35
- package/dist/locales/es.mjs +178 -19
- package/dist/locales/fr.mjs +150 -19
- package/dist/locales/nl.mjs +118 -19
- package/dist/locales/pl.mjs +156 -18
- package/dist/locales/shared.mjs +64 -1
- package/dist/locales/tr.mjs +138 -24
- package/dist/natural/correction.d.mts +1 -1
- package/dist/natural/correction.mjs +31 -5
- package/dist/natural/format.mjs +1 -1
- package/dist/natural/parse.mjs +5 -4
- package/dist/natural/suggest.mjs +19 -3
- package/dist/natural/suggestion.mjs +11 -5
- package/dist/resolve/resolve.mjs +1 -1
- package/package.json +1 -1
package/dist/locales/de.mjs
CHANGED
|
@@ -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, 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, openBoundaryCandidate, 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/de.ts
|
|
10
10
|
const months = [
|
|
@@ -21,6 +21,42 @@ const months = [
|
|
|
21
21
|
"november",
|
|
22
22
|
"dezember"
|
|
23
23
|
];
|
|
24
|
+
const germanCountWords = [
|
|
25
|
+
"zwei",
|
|
26
|
+
"drei",
|
|
27
|
+
"vier",
|
|
28
|
+
"fünf",
|
|
29
|
+
"sechs",
|
|
30
|
+
"sieben",
|
|
31
|
+
"acht",
|
|
32
|
+
"neun",
|
|
33
|
+
"zehn",
|
|
34
|
+
"elf",
|
|
35
|
+
"zwölf",
|
|
36
|
+
"dreizehn",
|
|
37
|
+
"vierzehn",
|
|
38
|
+
"fünfzehn",
|
|
39
|
+
"sechzehn",
|
|
40
|
+
"siebzehn",
|
|
41
|
+
"achtzehn",
|
|
42
|
+
"neunzehn",
|
|
43
|
+
"zwanzig"
|
|
44
|
+
];
|
|
45
|
+
const germanCountOnes = [[1, "ein"], ...germanCountWords.slice(0, 8).map((word, index) => [index + 2, word])];
|
|
46
|
+
const germanCountAliases = [...sequentialCountAliases(germanCountWords.map((word) => [word]), 2), ...compoundCountAliases(decimalTens([
|
|
47
|
+
"zwanzig",
|
|
48
|
+
"dreißig",
|
|
49
|
+
"vierzig",
|
|
50
|
+
"fünfzig",
|
|
51
|
+
"sechzig",
|
|
52
|
+
"siebzig",
|
|
53
|
+
"achtzig",
|
|
54
|
+
"neunzig"
|
|
55
|
+
]), germanCountOnes, (ten, one) => [`${one}und${ten}`])];
|
|
56
|
+
const normalizeGermanCounts = compileCountAliasNormalizer(germanCountAliases);
|
|
57
|
+
const germanCountVocabulary = new Set(countAliasVocabulary(germanCountAliases));
|
|
58
|
+
const correctGerman = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, germanCountVocabulary);
|
|
59
|
+
const normalizeGerman = (input, locale) => normalizeGermanCounts(normalizeNaturalText(input, locale));
|
|
24
60
|
const monthAbbreviations = [
|
|
25
61
|
["jan"],
|
|
26
62
|
["feb"],
|
|
@@ -118,6 +154,24 @@ const unitPhrases = [
|
|
|
118
154
|
}
|
|
119
155
|
];
|
|
120
156
|
const title = (value) => `${value.slice(0, 1).toLocaleUpperCase("de")}${value.slice(1)}`;
|
|
157
|
+
const weekdays = [
|
|
158
|
+
"montag",
|
|
159
|
+
"dienstag",
|
|
160
|
+
"mittwoch",
|
|
161
|
+
"donnerstag",
|
|
162
|
+
"freitag",
|
|
163
|
+
"samstag",
|
|
164
|
+
"sonntag"
|
|
165
|
+
];
|
|
166
|
+
const nextWeekdays = weekdays.flatMap((weekday, day) => [{
|
|
167
|
+
phrase: `nächster ${weekday}`,
|
|
168
|
+
day,
|
|
169
|
+
canonical: `nächster ${title(weekday)}`
|
|
170
|
+
}, {
|
|
171
|
+
phrase: `nächsten ${weekday}`,
|
|
172
|
+
day,
|
|
173
|
+
canonical: `nächster ${title(weekday)}`
|
|
174
|
+
}]);
|
|
121
175
|
const canonicalToDate = (entry) => `seit ${title(entry.compound)}beginn`;
|
|
122
176
|
const canonicalRelative = (value) => {
|
|
123
177
|
const separator = value.lastIndexOf(" ");
|
|
@@ -595,8 +649,8 @@ const quarterNumber = (value) => {
|
|
|
595
649
|
return index === -1 ? void 0 : index + 1;
|
|
596
650
|
};
|
|
597
651
|
const relativeDirection = (value) => {
|
|
598
|
-
if (value
|
|
599
|
-
if (value
|
|
652
|
+
if (value.startsWith("letzt")) return -1;
|
|
653
|
+
if (value.startsWith("nächst")) return 1;
|
|
600
654
|
return 0;
|
|
601
655
|
};
|
|
602
656
|
const parseQuarter = (input) => {
|
|
@@ -624,13 +678,13 @@ const parseQuarter = (input) => {
|
|
|
624
678
|
const quarter = quarterNumber(textAt(standalone.value, 1));
|
|
625
679
|
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
|
|
626
680
|
};
|
|
627
|
-
const
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (Option.isSome(
|
|
681
|
+
const parseDatedPeriod = (input) => {
|
|
682
|
+
const knownPeriod = Option.firstSomeOf([
|
|
683
|
+
absoluteDatePeriod(input, "de"),
|
|
684
|
+
parseNamedDate(input),
|
|
685
|
+
parseQuarter(input)
|
|
686
|
+
]);
|
|
687
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
634
688
|
const yearMatch = String$1.match(/^(?:(?:das )?(?:kalender)?jahr )?(\d{4})$/u)(input);
|
|
635
689
|
if (Option.isSome(yearMatch)) {
|
|
636
690
|
const year = validYear(textAt(yearMatch.value, 1));
|
|
@@ -642,6 +696,18 @@ const parseBasePeriod = (input) => {
|
|
|
642
696
|
const year = validYear(textAt(monthYear.value, 2));
|
|
643
697
|
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
|
|
644
698
|
}
|
|
699
|
+
return Option.none();
|
|
700
|
+
};
|
|
701
|
+
const parseRelativeMonth = (input) => {
|
|
702
|
+
const prefixedRelativeMonth = String$1.match(/^(letzter|dieser|nächster) ([a-zäöüß]+\.?)$/u)(input);
|
|
703
|
+
if (Option.isSome(prefixedRelativeMonth)) {
|
|
704
|
+
const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
|
|
705
|
+
if (month !== void 0) {
|
|
706
|
+
const modifier = textAt(prefixedRelativeMonth.value, 1);
|
|
707
|
+
const direction = relativeDirection(modifier);
|
|
708
|
+
return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${title(textAt(months, month - 1))}`));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
645
711
|
const relativeMonth = String$1.match(/^([a-zäöüß]+\.?) (letzten|dieses|nächsten) jahres$/u)(input);
|
|
646
712
|
if (Option.isSome(relativeMonth)) {
|
|
647
713
|
const directionText = textAt(relativeMonth.value, 2);
|
|
@@ -651,11 +717,18 @@ const parseBasePeriod = (input) => {
|
|
|
651
717
|
}
|
|
652
718
|
const standaloneMonth = monthNumber(input);
|
|
653
719
|
if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(textAt(months, standaloneMonth - 1))));
|
|
720
|
+
return Option.none();
|
|
721
|
+
};
|
|
722
|
+
const parseBasePeriod = (input) => {
|
|
723
|
+
const knownPeriod = Option.firstSomeOf([parseDatedPeriod(input), parseRelativeMonth(input)]);
|
|
724
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
654
725
|
if (input === "wochenende" || input === "dieses wochenende" || input === "am wochenende") return Option.some(relativeWeekend(0, "dieses Wochenende"));
|
|
655
726
|
if (input === "letztes wochenende" || input === "vergangenes wochenende") return Option.some(relativeWeekend(-1, "letztes Wochenende"));
|
|
656
727
|
if (input === "nächstes wochenende" || input === "kommendes wochenende") return Option.some(relativeWeekend(1, "nächstes Wochenende"));
|
|
657
728
|
if (input === "vorletztes wochenende") return Option.some(relativeWeekend(-2, "vorletztes Wochenende"));
|
|
658
729
|
if (input === "übernächstes wochenende") return Option.some(relativeWeekend(2, "übernächstes Wochenende"));
|
|
730
|
+
const weekday = nextWeekdays.find((entry) => entry.phrase === input);
|
|
731
|
+
if (weekday !== void 0) return Option.some(relativeWeekday(weekday.day, 1, weekday.canonical));
|
|
659
732
|
const relative = unitPhrases.find((entry) => entry.current === input || entry.previous === input || entry.next === input);
|
|
660
733
|
if (relative !== void 0) {
|
|
661
734
|
let direction = 0;
|
|
@@ -668,10 +741,31 @@ const parseBasePeriod = (input) => {
|
|
|
668
741
|
return Option.none();
|
|
669
742
|
};
|
|
670
743
|
const parsePeriod = (input) => {
|
|
744
|
+
const shifted = String$1.match(/^(.+) (vor|in) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
|
|
745
|
+
if (Option.isSome(shifted)) {
|
|
746
|
+
const amount = parseTrailingCount(textAt(shifted.value, 3));
|
|
747
|
+
const unitText = textAt(shifted.value, 4);
|
|
748
|
+
const entry = unitPhrases.find((unit) => [
|
|
749
|
+
unit.noun,
|
|
750
|
+
unit.plural,
|
|
751
|
+
unit.dative
|
|
752
|
+
].includes(unitText));
|
|
753
|
+
const period = parsePeriod(textAt(shifted.value, 1));
|
|
754
|
+
if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
|
|
755
|
+
const past = textAt(shifted.value, 2) === "vor";
|
|
756
|
+
const direction = past ? -amount.value : amount.value;
|
|
757
|
+
const noun = amount.value === 1 ? entry.noun : entry.dative;
|
|
758
|
+
const canonical = `${period.value.canonical} ${past ? "vor" : "in"} ${amount.value} ${title(noun)}`;
|
|
759
|
+
return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
671
762
|
const edge = String$1.match(/^(anfang|beginn|ende) (.+)$/u)(input);
|
|
672
763
|
if (Option.isSome(edge)) {
|
|
673
764
|
const edgeName = textAt(edge.value, 1);
|
|
674
|
-
const
|
|
765
|
+
const periodText = textAt(edge.value, 2);
|
|
766
|
+
const basePeriod = parseBasePeriod(periodText);
|
|
767
|
+
const implicit = unitPhrases.find((entry) => entry.genitive === periodText);
|
|
768
|
+
const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
|
|
675
769
|
if (Option.isSome(period)) {
|
|
676
770
|
const canonical = `${edgeName === "ende" ? "Ende" : "Anfang"} ${withRelativeGenitive(period.value.canonical)}`;
|
|
677
771
|
return Option.some(edgeName === "ende" ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
@@ -683,7 +777,8 @@ const parsePeriod = (input) => {
|
|
|
683
777
|
"für ",
|
|
684
778
|
"während "
|
|
685
779
|
].find((prefix) => input.startsWith(prefix));
|
|
686
|
-
|
|
780
|
+
const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
|
|
781
|
+
return Option.isSome(base) ? base : parseCalendarOffset(input);
|
|
687
782
|
};
|
|
688
783
|
const currentBoundaryCandidate = (input) => {
|
|
689
784
|
const match = currentBoundaryPhrases.find((entry) => entry.phrase === input);
|
|
@@ -731,7 +826,7 @@ const parseCalendarOffset = (input) => {
|
|
|
731
826
|
const isPast = textAt(singular.value, 1) === "vor";
|
|
732
827
|
const direction = isPast ? -1 : 1;
|
|
733
828
|
const canonical = `${isPast ? "vor" : "in"} ${canonicalRelative(entry.indefiniteDative)}`;
|
|
734
|
-
return Option.some(
|
|
829
|
+
return Option.some(relativePeriod(entry.unit, direction, canonical));
|
|
735
830
|
}
|
|
736
831
|
}
|
|
737
832
|
const prefixed = String$1.match(/^(vor|in) ([1-9]\d*) (tag|tagen|woche|wochen|monat|monaten|quartal|quartalen|jahr|jahren)$/u)(input);
|
|
@@ -749,7 +844,7 @@ const parseCalendarOffset = (input) => {
|
|
|
749
844
|
const directionName = isPast ? "vor" : "in";
|
|
750
845
|
const noun = amount.value === 1 ? entry.noun : entry.dative;
|
|
751
846
|
const canonical = `${directionName} ${amount.value} ${title(noun)}`;
|
|
752
|
-
return Option.some(
|
|
847
|
+
return Option.some(relativePeriod(entry.unit, direction, canonical));
|
|
753
848
|
};
|
|
754
849
|
const parseRollingPeriod = (input) => {
|
|
755
850
|
const past = String$1.match(/^(?:(?:die letzten|letzten|letzte|vergangene|vorherige) )?([1-9]\d*) (tag|tage|woche|wochen|monat|monate|quartal|quartale|jahr|jahre)$/u)(input);
|
|
@@ -804,8 +899,6 @@ const parseGerman = (input) => {
|
|
|
804
899
|
if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry.unit), `Rest ${canonicalRelative(remaining.entry.genitive)}`));
|
|
805
900
|
if (["bis heute", "bis jetzt"].includes(input)) return Option.some(candidate(untilNowRange(), "bis heute"));
|
|
806
901
|
if (["ab jetzt", "von jetzt an"].includes(input)) return Option.some(candidate(fromNowRange(), "ab jetzt"));
|
|
807
|
-
const offset = parseCalendarOffset(input);
|
|
808
|
-
if (Option.isSome(offset)) return offset;
|
|
809
902
|
const rolling = parseRollingPeriod(input);
|
|
810
903
|
if (Option.isSome(rolling)) return rolling;
|
|
811
904
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
@@ -818,7 +911,12 @@ const parseGerman = (input) => {
|
|
|
818
911
|
["von ", " bis heute"],
|
|
819
912
|
["vom ", " bis heute"],
|
|
820
913
|
["zwischen ", " und heute"]
|
|
821
|
-
], [
|
|
914
|
+
], [
|
|
915
|
+
"von heute bis ",
|
|
916
|
+
"zwischen heute und ",
|
|
917
|
+
"heute bis ",
|
|
918
|
+
"jetzt bis "
|
|
919
|
+
], parsePeriod, (period) => `von ${period} bis heute`, (period) => `von heute bis ${period}`);
|
|
822
920
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
823
921
|
const bounded = joinedPeriodCandidate(input, [
|
|
824
922
|
["von ", " bis einschließlich "],
|
|
@@ -877,9 +975,12 @@ const staticPeriodPhrases = [
|
|
|
877
975
|
]),
|
|
878
976
|
...months.flatMap((month) => [
|
|
879
977
|
month,
|
|
978
|
+
`letzter ${month}`,
|
|
979
|
+
`nächster ${month}`,
|
|
880
980
|
`${month} letzten jahres`,
|
|
881
981
|
`${month} nächsten jahres`
|
|
882
|
-
])
|
|
982
|
+
]),
|
|
983
|
+
...nextWeekdays.map((entry) => entry.phrase)
|
|
883
984
|
];
|
|
884
985
|
const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
|
|
885
986
|
const boundaryPrefixes = [
|
|
@@ -936,6 +1037,17 @@ const suggestGerman = (input, limit) => {
|
|
|
936
1037
|
], limit);
|
|
937
1038
|
};
|
|
938
1039
|
const renderGerman = (range) => {
|
|
1040
|
+
const shifted = decomposeShiftedPeriodRange(range);
|
|
1041
|
+
if (Option.isSome(shifted)) {
|
|
1042
|
+
const base = renderGerman(shifted.value.baseRange);
|
|
1043
|
+
const entry = unitPhrases.find((unit) => unit.unit === shifted.value.unit);
|
|
1044
|
+
if (Option.isSome(base) && entry !== void 0) {
|
|
1045
|
+
const amount = Math.abs(shifted.value.amount);
|
|
1046
|
+
const noun = amount === 1 ? entry.noun : entry.dative;
|
|
1047
|
+
const direction = shifted.value.amount < 0 ? "vor" : "in";
|
|
1048
|
+
return Option.some(`${base.value} ${direction} ${amount} ${title(noun)}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
939
1051
|
const offset = calendarPeriodOffset(range);
|
|
940
1052
|
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
941
1053
|
const entry = unitPhrases.find((unit) => unit.unit === offset.value.unit);
|
|
@@ -963,6 +1075,7 @@ const GermanContribution = new BaseLanguageContribution({
|
|
|
963
1075
|
locale: "de",
|
|
964
1076
|
vocabulary: [
|
|
965
1077
|
...months,
|
|
1078
|
+
...weekdays,
|
|
966
1079
|
...quarterNames,
|
|
967
1080
|
"q1",
|
|
968
1081
|
"q2",
|
|
@@ -1024,8 +1137,8 @@ const GermanContribution = new BaseLanguageContribution({
|
|
|
1024
1137
|
"zwischen",
|
|
1025
1138
|
"zuvor"
|
|
1026
1139
|
],
|
|
1027
|
-
normalize:
|
|
1028
|
-
correct:
|
|
1140
|
+
normalize: normalizeGerman,
|
|
1141
|
+
correct: correctGerman,
|
|
1029
1142
|
parseExact: parseGerman,
|
|
1030
1143
|
suggest: suggestGerman,
|
|
1031
1144
|
render: renderGerman
|
package/dist/locales/en.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { normalizeNaturalText } from "../natural/text.mjs";
|
|
|
3
3
|
import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
|
|
4
4
|
import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
|
|
5
5
|
import { completeNaturalPhrases, fixedCalendarPeriodPhrases, naturalCount, prefixNaturalPhrases } from "../natural/suggestion.mjs";
|
|
6
|
-
import { absoluteDatePeriod, calendarPeriodOffset, candidate, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodEndDay, periodPreviousDay, 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, periodPreviousDay, periodRange, periodStartDay, periodToDateRange, periodsFromPhrases, quarterOfRelativeYear, relativePeriod, relativeWeekday, relativeWeekend, remainingPeriodRange, renderPeriodRange, sequentialCountAliases, shiftPeriod, textAt, trailingPeriod, trailingRange, untilNowRange, validYear } from "./shared.mjs";
|
|
7
7
|
import { Effect, Option, String as String$1 } from "effect";
|
|
8
8
|
//#region src/locales/en.ts
|
|
9
9
|
const months = [
|
|
@@ -34,12 +34,59 @@ const monthAbbreviations = [
|
|
|
34
34
|
["nov"],
|
|
35
35
|
["dec"]
|
|
36
36
|
];
|
|
37
|
+
const weekdays = [
|
|
38
|
+
"monday",
|
|
39
|
+
"tuesday",
|
|
40
|
+
"wednesday",
|
|
41
|
+
"thursday",
|
|
42
|
+
"friday",
|
|
43
|
+
"saturday",
|
|
44
|
+
"sunday"
|
|
45
|
+
];
|
|
46
|
+
const nextWeekdayPhrases = weekdays.map((weekday) => `next ${weekday}`);
|
|
37
47
|
const quarterNames = [
|
|
38
48
|
"first",
|
|
39
49
|
"second",
|
|
40
50
|
"third",
|
|
41
51
|
"fourth"
|
|
42
52
|
];
|
|
53
|
+
const englishCountWords = [
|
|
54
|
+
"one",
|
|
55
|
+
"two",
|
|
56
|
+
"three",
|
|
57
|
+
"four",
|
|
58
|
+
"five",
|
|
59
|
+
"six",
|
|
60
|
+
"seven",
|
|
61
|
+
"eight",
|
|
62
|
+
"nine",
|
|
63
|
+
"ten",
|
|
64
|
+
"eleven",
|
|
65
|
+
"twelve",
|
|
66
|
+
"thirteen",
|
|
67
|
+
"fourteen",
|
|
68
|
+
"fifteen",
|
|
69
|
+
"sixteen",
|
|
70
|
+
"seventeen",
|
|
71
|
+
"eighteen",
|
|
72
|
+
"nineteen",
|
|
73
|
+
"twenty"
|
|
74
|
+
];
|
|
75
|
+
const englishCountOnes = englishCountWords.slice(0, 9).map((word, index) => [index + 1, word]);
|
|
76
|
+
const englishCountAliases = [...sequentialCountAliases(englishCountWords.map((word) => [word]), 1), ...compoundCountAliases(decimalTens([
|
|
77
|
+
"twenty",
|
|
78
|
+
"thirty",
|
|
79
|
+
"forty",
|
|
80
|
+
"fifty",
|
|
81
|
+
"sixty",
|
|
82
|
+
"seventy",
|
|
83
|
+
"eighty",
|
|
84
|
+
"ninety"
|
|
85
|
+
]), englishCountOnes, (ten, one) => [`${ten} ${one}`, `${ten}-${one}`])];
|
|
86
|
+
const normalizeEnglishCounts = compileCountAliasNormalizer(englishCountAliases);
|
|
87
|
+
const englishCountVocabulary = new Set(countAliasVocabulary(englishCountAliases));
|
|
88
|
+
const correctEnglish = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, englishCountVocabulary);
|
|
89
|
+
const normalizeEnglish = (input, locale) => normalizeEnglishCounts(normalizeNaturalText(input, locale));
|
|
43
90
|
const units = [
|
|
44
91
|
[
|
|
45
92
|
"day",
|
|
@@ -170,13 +217,13 @@ const parseNamedDate = (input) => {
|
|
|
170
217
|
if (Option.isNone(current)) return Option.none();
|
|
171
218
|
return Option.isSome(currentDayFirst) ? namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel) : namedCurrentYearDatePeriod(textAt(current.value, 1), textAt(current.value, 2), monthNumber, currentDateLabel);
|
|
172
219
|
};
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (Option.isSome(
|
|
220
|
+
const parseDatedPeriod = (input) => {
|
|
221
|
+
const knownPeriod = Option.firstSomeOf([
|
|
222
|
+
absoluteDatePeriod(input, "en"),
|
|
223
|
+
parseNamedDate(input),
|
|
224
|
+
parseQuarter(input)
|
|
225
|
+
]);
|
|
226
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
180
227
|
const yearMatch = String$1.match(/^(?:(?:the )?(?:calendar )?year )?(\d{4})$/u)(input);
|
|
181
228
|
if (Option.isSome(yearMatch)) {
|
|
182
229
|
const year = validYear(textAt(yearMatch.value, 1));
|
|
@@ -188,6 +235,15 @@ const parseBasePeriod = (input) => {
|
|
|
188
235
|
const year = validYear(textAt(monthYear.value, 2));
|
|
189
236
|
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
|
|
190
237
|
}
|
|
238
|
+
return Option.none();
|
|
239
|
+
};
|
|
240
|
+
const parseRelativeMonth = (input) => {
|
|
241
|
+
const prefixedRelativeMonth = String$1.match(/^(last|this|next) ([a-z]+\.?)$/u)(input);
|
|
242
|
+
if (Option.isSome(prefixedRelativeMonth)) {
|
|
243
|
+
const directionText = textAt(prefixedRelativeMonth.value, 1);
|
|
244
|
+
const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
|
|
245
|
+
if (month !== void 0) return Option.some(monthOfRelativeYear(month, relativeDirection(directionText), `${directionText} ${title(textAt(months, month - 1))}`));
|
|
246
|
+
}
|
|
191
247
|
const relativeMonth = String$1.match(/^([a-z]+\.?) (?:of )?(last|this|next) year$/u)(input);
|
|
192
248
|
if (Option.isSome(relativeMonth)) {
|
|
193
249
|
const directionText = textAt(relativeMonth.value, 2);
|
|
@@ -197,14 +253,9 @@ const parseBasePeriod = (input) => {
|
|
|
197
253
|
}
|
|
198
254
|
const standaloneMonth = monthNumber(input);
|
|
199
255
|
if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(textAt(months, standaloneMonth - 1))));
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (input === "weekend" || input === "the weekend" || input === "this weekend") return Option.some(relativeWeekend(0, "this weekend"));
|
|
204
|
-
if (input === "last weekend" || input === "previous weekend") return Option.some(relativeWeekend(-1, "last weekend"));
|
|
205
|
-
if (input === "next weekend" || input === "coming weekend") return Option.some(relativeWeekend(1, "next weekend"));
|
|
206
|
-
if (input === "the weekend before last") return Option.some(relativeWeekend(-2, "the weekend before last"));
|
|
207
|
-
if (input === "the weekend after next") return Option.some(relativeWeekend(2, "the weekend after next"));
|
|
256
|
+
return Option.none();
|
|
257
|
+
};
|
|
258
|
+
const parseRelativeUnit = (input) => {
|
|
208
259
|
const articlePeriod = String$1.match(/^the (day|week|month|quarter|year)$/u)(input);
|
|
209
260
|
if (Option.isSome(articlePeriod)) {
|
|
210
261
|
const entry = units.find((unit) => unit[0] === textAt(articlePeriod.value, 1));
|
|
@@ -230,22 +281,69 @@ const parseBasePeriod = (input) => {
|
|
|
230
281
|
}
|
|
231
282
|
return Option.none();
|
|
232
283
|
};
|
|
284
|
+
const parseBasePeriod = (input) => {
|
|
285
|
+
const knownPeriod = Option.firstSomeOf([parseDatedPeriod(input), parseRelativeMonth(input)]);
|
|
286
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
287
|
+
if (input === "today") return Option.some(relativePeriod("day", 0, "today"));
|
|
288
|
+
if (input === "yesterday") return Option.some(relativePeriod("day", -1, "yesterday"));
|
|
289
|
+
if (input === "tomorrow") return Option.some(relativePeriod("day", 1, "tomorrow"));
|
|
290
|
+
if (input === "weekend" || input === "the weekend" || input === "this weekend") return Option.some(relativeWeekend(0, "this weekend"));
|
|
291
|
+
if (input === "last weekend" || input === "previous weekend") return Option.some(relativeWeekend(-1, "last weekend"));
|
|
292
|
+
if (input === "next weekend" || input === "coming weekend") return Option.some(relativeWeekend(1, "next weekend"));
|
|
293
|
+
if (input === "the weekend before last") return Option.some(relativeWeekend(-2, "the weekend before last"));
|
|
294
|
+
if (input === "the weekend after next") return Option.some(relativeWeekend(2, "the weekend after next"));
|
|
295
|
+
const weekday = nextWeekdayPhrases.indexOf(input);
|
|
296
|
+
if (weekday !== -1) return Option.some(relativeWeekday(weekday, 1, `next ${title(textAt(weekdays, weekday))}`));
|
|
297
|
+
return parseRelativeUnit(input);
|
|
298
|
+
};
|
|
299
|
+
const parsePeriodEdge = (input) => {
|
|
300
|
+
const edge = String$1.match(/^(?:the )?(start|beginning|end) of (.+)$/u)(input);
|
|
301
|
+
if (Option.isNone(edge)) return Option.none();
|
|
302
|
+
const periodText = textAt(edge.value, 2);
|
|
303
|
+
const basePeriod = parseBasePeriod(periodText);
|
|
304
|
+
const implicit = units.find((entry) => entry[0] === periodText);
|
|
305
|
+
const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit[1], 0, currentPeriod(implicit[0])));
|
|
306
|
+
if (Option.isNone(period)) return Option.none();
|
|
307
|
+
const edgeName = textAt(edge.value, 1);
|
|
308
|
+
const name = edgeName === "beginning" ? "start" : edgeName;
|
|
309
|
+
const canonical = `${name} of ${period.value.canonical}`;
|
|
310
|
+
return Option.some(name === "end" ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
311
|
+
};
|
|
233
312
|
const parsePeriod = (input) => {
|
|
313
|
+
const shifted = String$1.match(/^(.+) ([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (ago|prior|from now)$/u)(input);
|
|
314
|
+
if (Option.isSome(shifted)) {
|
|
315
|
+
const amount = parseTrailingCount(textAt(shifted.value, 2));
|
|
316
|
+
const unitText = textAt(shifted.value, 3);
|
|
317
|
+
const period = parsePeriod(textAt(shifted.value, 1));
|
|
318
|
+
if (Option.isSome(amount) && Option.isSome(period)) {
|
|
319
|
+
const entry = countedUnit(unitText, amount.value);
|
|
320
|
+
if (entry !== void 0) {
|
|
321
|
+
const past = textAt(shifted.value, 4) !== "from now";
|
|
322
|
+
const direction = past ? -amount.value : amount.value;
|
|
323
|
+
const suffix = past ? "ago" : "from now";
|
|
324
|
+
const canonical = `${period.value.canonical} ${amount.value} ${unitText} ${suffix}`;
|
|
325
|
+
return Option.some(shiftPeriod(period.value, direction, entry[1], canonical));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const relativeShift = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (before|after) (.+)$/u)(input);
|
|
330
|
+
if (Option.isSome(relativeShift)) {
|
|
331
|
+
const amount = parseTrailingCount(textAt(relativeShift.value, 1));
|
|
332
|
+
const unitText = textAt(relativeShift.value, 2);
|
|
333
|
+
const period = parsePeriod(textAt(relativeShift.value, 4));
|
|
334
|
+
if (Option.isSome(amount) && Option.isSome(period)) {
|
|
335
|
+
const entry = countedUnit(unitText, amount.value);
|
|
336
|
+
if (entry !== void 0) {
|
|
337
|
+
const direction = textAt(relativeShift.value, 3) === "before" ? -amount.value : amount.value;
|
|
338
|
+
return Option.some(shiftPeriod(period.value, direction, entry[1], input));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
234
342
|
const previousDay = String$1.match(/^(?:the )?day before (.+)$/u)(input);
|
|
235
343
|
if (Option.isSome(previousDay)) {
|
|
236
344
|
const period = parseBasePeriod(textAt(previousDay.value, 1));
|
|
237
345
|
if (Option.isSome(period)) return Option.some(periodPreviousDay(period.value, `the day before ${period.value.canonical}`));
|
|
238
346
|
}
|
|
239
|
-
const edge = String$1.match(/^(?:the )?(start|beginning|end) of (.+)$/u)(input);
|
|
240
|
-
if (Option.isSome(edge)) {
|
|
241
|
-
const period = parseBasePeriod(textAt(edge.value, 2));
|
|
242
|
-
if (Option.isSome(period)) {
|
|
243
|
-
const edgeName = textAt(edge.value, 1);
|
|
244
|
-
const name = edgeName === "beginning" ? "start" : edgeName;
|
|
245
|
-
const canonical = `${name} of ${period.value.canonical}`;
|
|
246
|
-
return Option.some(name === "end" ? periodEndDay(period.value, canonical) : periodStartDay(period.value, canonical));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
347
|
const wrapper = [
|
|
250
348
|
"during ",
|
|
251
349
|
"in ",
|
|
@@ -253,7 +351,11 @@ const parsePeriod = (input) => {
|
|
|
253
351
|
"all of ",
|
|
254
352
|
"the whole of "
|
|
255
353
|
].find((prefix) => input.startsWith(prefix));
|
|
256
|
-
return
|
|
354
|
+
return Option.firstSomeOf([
|
|
355
|
+
parsePeriodEdge(input),
|
|
356
|
+
parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length)),
|
|
357
|
+
parseCalendarOffset(input)
|
|
358
|
+
]);
|
|
257
359
|
};
|
|
258
360
|
const boundaryCandidate = (input) => openBoundaryCandidate(input, [
|
|
259
361
|
["up to and including ", "through"],
|
|
@@ -296,7 +398,7 @@ const parseCalendarOffset = (input) => {
|
|
|
296
398
|
const isPast = directionText === "ago" || directionText === "prior";
|
|
297
399
|
const direction = isPast ? -1 : 1;
|
|
298
400
|
const canonical = isPast ? `1 ${entry[0]} ago` : `in 1 ${entry[0]}`;
|
|
299
|
-
return Option.some(
|
|
401
|
+
return Option.some(relativePeriod(entry[1], direction, canonical));
|
|
300
402
|
}
|
|
301
403
|
}
|
|
302
404
|
const past = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (?:ago|prior)$/u)(input);
|
|
@@ -315,7 +417,7 @@ const parseCalendarOffset = (input) => {
|
|
|
315
417
|
if (entry === void 0) return Option.none();
|
|
316
418
|
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
317
419
|
const canonical = direction < 0 ? `${amount.value} ${unitText} ago` : `in ${amount.value} ${unitText}`;
|
|
318
|
-
return Option.some(
|
|
420
|
+
return Option.some(relativePeriod(entry[1], direction, canonical));
|
|
319
421
|
};
|
|
320
422
|
const parseRollingPeriod = (input) => {
|
|
321
423
|
const past = String$1.match(/^(?:(?:last|previous|past) |(?:in|over) the (?:last|previous|past) )?([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years)$/u)(input);
|
|
@@ -371,8 +473,6 @@ const parseEnglish = (input) => {
|
|
|
371
473
|
"from now on",
|
|
372
474
|
"starting now"
|
|
373
475
|
].includes(input)) return Option.some(candidate(fromNowRange(), "from now"));
|
|
374
|
-
const offset = parseCalendarOffset(input);
|
|
375
|
-
if (Option.isSome(offset)) return offset;
|
|
376
476
|
const rolling = parseRollingPeriod(input);
|
|
377
477
|
if (Option.isSome(rolling)) return rolling;
|
|
378
478
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
@@ -389,7 +489,8 @@ const parseEnglish = (input) => {
|
|
|
389
489
|
"from now to ",
|
|
390
490
|
"from now until ",
|
|
391
491
|
"from now through ",
|
|
392
|
-
"between now and "
|
|
492
|
+
"between now and ",
|
|
493
|
+
"now to "
|
|
393
494
|
], parsePeriod, (period) => `from ${period} to now`, (period) => `from now to ${period}`);
|
|
394
495
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
395
496
|
const bounded = joinedPeriodCandidate(input, [
|
|
@@ -457,9 +558,12 @@ const staticPeriodPhrases = [
|
|
|
457
558
|
]),
|
|
458
559
|
...months.flatMap((month) => [
|
|
459
560
|
month,
|
|
561
|
+
`last ${month}`,
|
|
562
|
+
`next ${month}`,
|
|
460
563
|
`${month} of last year`,
|
|
461
564
|
`${month} of next year`
|
|
462
|
-
])
|
|
565
|
+
]),
|
|
566
|
+
...nextWeekdayPhrases
|
|
463
567
|
];
|
|
464
568
|
const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
|
|
465
569
|
const boundaryPrefixes = [
|
|
@@ -501,6 +605,17 @@ const suggestEnglish = (input, limit) => {
|
|
|
501
605
|
], limit);
|
|
502
606
|
};
|
|
503
607
|
const renderEnglish = (range) => {
|
|
608
|
+
const shifted = decomposeShiftedPeriodRange(range);
|
|
609
|
+
if (Option.isSome(shifted)) {
|
|
610
|
+
const base = renderEnglish(shifted.value.baseRange);
|
|
611
|
+
const entry = units.find((unit) => unit[1] === shifted.value.unit);
|
|
612
|
+
if (Option.isSome(base) && entry !== void 0) {
|
|
613
|
+
const amount = Math.abs(shifted.value.amount);
|
|
614
|
+
const noun = amount === 1 ? entry[0] : entry[2];
|
|
615
|
+
const suffix = shifted.value.amount < 0 ? "ago" : "from now";
|
|
616
|
+
return Option.some(`${base.value} ${amount} ${noun} ${suffix}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
504
619
|
const offset = calendarPeriodOffset(range);
|
|
505
620
|
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
506
621
|
const entry = units.find((unit) => unit[1] === offset.value.unit);
|
|
@@ -530,6 +645,7 @@ const EnglishContribution = new BaseLanguageContribution({
|
|
|
530
645
|
locale: "en",
|
|
531
646
|
vocabulary: [
|
|
532
647
|
...months,
|
|
648
|
+
...weekdays,
|
|
533
649
|
...quarterNames,
|
|
534
650
|
"q1",
|
|
535
651
|
"q2",
|
|
@@ -583,8 +699,8 @@ const EnglishContribution = new BaseLanguageContribution({
|
|
|
583
699
|
"weekend",
|
|
584
700
|
"yesterday"
|
|
585
701
|
],
|
|
586
|
-
normalize:
|
|
587
|
-
correct:
|
|
702
|
+
normalize: normalizeEnglish,
|
|
703
|
+
correct: correctEnglish,
|
|
588
704
|
parseExact: parseEnglish,
|
|
589
705
|
suggest: suggestEnglish,
|
|
590
706
|
render: renderEnglish
|