chronolizer 0.1.0 → 0.3.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 +184 -119
- package/dist/ast/constructors.d.mts +135 -0
- package/dist/ast/constructors.mjs +25 -0
- package/dist/ast/fold.d.mts +13 -0
- package/dist/ast/fold.mjs +58 -0
- package/dist/ast/normalize.d.mts +42 -0
- package/dist/ast/normalize.mjs +34 -0
- package/dist/ast/schemas.d.mts +80 -0
- package/dist/ast/schemas.mjs +65 -0
- package/dist/filter/codec.d.mts +92 -0
- package/dist/filter/codec.mjs +48 -0
- package/dist/filter/errors.d.mts +14 -0
- package/dist/filter/errors.mjs +10 -0
- package/dist/filter/expression.d.mts +8 -0
- package/dist/filter/expression.mjs +76 -0
- package/dist/filter/schema.d.mts +13 -0
- package/dist/filter/schema.mjs +11 -0
- package/dist/filter/transformation.d.mts +37 -0
- package/dist/filter/transformation.mjs +20 -0
- package/dist/index.d.mts +22 -969
- package/dist/index.mjs +22 -2305
- package/dist/language/errors.d.mts +38 -0
- package/dist/language/errors.mjs +30 -0
- package/dist/language/model.d.mts +239 -0
- package/dist/language/model.mjs +50 -0
- package/dist/language/registry.d.mts +17 -0
- package/dist/language/registry.mjs +116 -0
- package/dist/locales/cs.d.mts +10 -0
- package/dist/locales/cs.mjs +746 -0
- package/dist/locales/de.d.mts +10 -0
- package/dist/locales/de.mjs +1039 -0
- package/dist/locales/en.d.mts +10 -0
- package/dist/locales/en.mjs +598 -0
- package/dist/locales/es.d.mts +10 -0
- package/dist/locales/es.mjs +908 -0
- package/dist/locales/fr.d.mts +10 -0
- package/dist/locales/fr.mjs +901 -0
- package/dist/locales/nl.d.mts +10 -0
- package/dist/locales/nl.mjs +791 -0
- package/dist/locales/pl.d.mts +10 -0
- package/dist/locales/pl.mjs +885 -0
- package/dist/locales/shared.mjs +395 -0
- package/dist/locales/tr.d.mts +10 -0
- package/dist/locales/tr.mjs +725 -0
- package/dist/natural/correction.d.mts +13 -0
- package/dist/natural/correction.mjs +73 -0
- package/dist/natural/format.d.mts +47 -0
- package/dist/natural/format.mjs +14 -0
- package/dist/natural/parse.d.mts +99 -0
- package/dist/natural/parse.mjs +66 -0
- package/dist/natural/policy.mjs +6 -0
- package/dist/natural/suggest.d.mts +53 -0
- package/dist/natural/suggest.mjs +24 -0
- package/dist/natural/suggestion.d.mts +4 -0
- package/dist/natural/suggestion.mjs +79 -0
- package/dist/natural/text.d.mts +5 -0
- package/dist/natural/text.mjs +5 -0
- package/dist/resolve/resolve.d.mts +79 -0
- package/dist/resolve/resolve.mjs +58 -0
- package/dist/resolve/schema.d.mts +59 -0
- package/dist/resolve/schema.mjs +28 -0
- package/package.json +15 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LanguageConflictError, LanguageRegistrationError } from "../language/errors.mjs";
|
|
2
|
+
import { BaseLanguageContribution, LanguagePlugin } from "../language/model.mjs";
|
|
3
|
+
import { LanguageRegistry } from "../language/registry.mjs";
|
|
4
|
+
import "../index.mjs";
|
|
5
|
+
//#region src/locales/en.d.ts
|
|
6
|
+
declare const EnglishContribution: BaseLanguageContribution;
|
|
7
|
+
declare const EnglishLanguage: LanguagePlugin;
|
|
8
|
+
declare const EnglishLanguageLayer: import("effect/Layer").Layer<LanguageRegistry, LanguageConflictError | LanguageRegistrationError, never>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { EnglishContribution, EnglishLanguage, EnglishLanguageLayer };
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { BaseLanguageContribution } from "../language/model.mjs";
|
|
2
|
+
import { normalizeNaturalText } from "../natural/text.mjs";
|
|
3
|
+
import { defineLanguagePlugin, languagePluginsLayer } from "../language/registry.mjs";
|
|
4
|
+
import { correctWhitespaceSeparatedText } from "../natural/correction.mjs";
|
|
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";
|
|
7
|
+
import { Effect, Option, String as String$1 } from "effect";
|
|
8
|
+
//#region src/locales/en.ts
|
|
9
|
+
const months = [
|
|
10
|
+
"january",
|
|
11
|
+
"february",
|
|
12
|
+
"march",
|
|
13
|
+
"april",
|
|
14
|
+
"may",
|
|
15
|
+
"june",
|
|
16
|
+
"july",
|
|
17
|
+
"august",
|
|
18
|
+
"september",
|
|
19
|
+
"october",
|
|
20
|
+
"november",
|
|
21
|
+
"december"
|
|
22
|
+
];
|
|
23
|
+
const monthAbbreviations = [
|
|
24
|
+
["jan"],
|
|
25
|
+
["feb"],
|
|
26
|
+
["mar"],
|
|
27
|
+
["apr"],
|
|
28
|
+
["may"],
|
|
29
|
+
["jun"],
|
|
30
|
+
["jul"],
|
|
31
|
+
["aug"],
|
|
32
|
+
["sep", "sept"],
|
|
33
|
+
["oct"],
|
|
34
|
+
["nov"],
|
|
35
|
+
["dec"]
|
|
36
|
+
];
|
|
37
|
+
const quarterNames = [
|
|
38
|
+
"first",
|
|
39
|
+
"second",
|
|
40
|
+
"third",
|
|
41
|
+
"fourth"
|
|
42
|
+
];
|
|
43
|
+
const units = [
|
|
44
|
+
[
|
|
45
|
+
"day",
|
|
46
|
+
"day",
|
|
47
|
+
"days"
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"week",
|
|
51
|
+
"week",
|
|
52
|
+
"weeks"
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
"month",
|
|
56
|
+
"month",
|
|
57
|
+
"months"
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"quarter",
|
|
61
|
+
"quarter",
|
|
62
|
+
"quarters"
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
"year",
|
|
66
|
+
"year",
|
|
67
|
+
"years"
|
|
68
|
+
]
|
|
69
|
+
];
|
|
70
|
+
const title = (value) => `${value.slice(0, 1).toLocaleUpperCase("en")}${value.slice(1)}`;
|
|
71
|
+
const currentPeriod = (unit) => unit === "day" ? "today" : `this ${unit}`;
|
|
72
|
+
const remainingPeriodPhrases = units.flatMap((entry) => [
|
|
73
|
+
`rest of the ${entry[0]}`,
|
|
74
|
+
`rest of ${entry[0]}`,
|
|
75
|
+
`rest of ${currentPeriod(entry[0])}`,
|
|
76
|
+
`remainder of the ${entry[0]}`,
|
|
77
|
+
`remaining ${entry[0]}`
|
|
78
|
+
].map((phrase) => ({
|
|
79
|
+
entry,
|
|
80
|
+
phrase
|
|
81
|
+
})));
|
|
82
|
+
const toDateAbbreviations = [
|
|
83
|
+
"dtd",
|
|
84
|
+
"wtd",
|
|
85
|
+
"mtd",
|
|
86
|
+
"qtd",
|
|
87
|
+
"ytd"
|
|
88
|
+
];
|
|
89
|
+
const toDatePhrases = units.flatMap((entry, index) => [
|
|
90
|
+
`${entry[0]} to date`,
|
|
91
|
+
`${entry[0]}-to-date`,
|
|
92
|
+
toDateAbbreviations[index] ?? "",
|
|
93
|
+
`since the start of the ${entry[0]}`,
|
|
94
|
+
`since the beginning of the ${entry[0]}`,
|
|
95
|
+
`from the start of the ${entry[0]} to now`,
|
|
96
|
+
`from the beginning of the ${entry[0]} to now`,
|
|
97
|
+
`${currentPeriod(entry[0])} so far`,
|
|
98
|
+
`so far ${currentPeriod(entry[0])}`,
|
|
99
|
+
`${currentPeriod(entry[0])} until now`,
|
|
100
|
+
`${currentPeriod(entry[0])} up to now`
|
|
101
|
+
].map((phrase) => ({
|
|
102
|
+
entry,
|
|
103
|
+
phrase
|
|
104
|
+
})));
|
|
105
|
+
const monthNumber = (value) => {
|
|
106
|
+
const normalized = value.endsWith(".") ? value.slice(0, -1) : value;
|
|
107
|
+
const fullIndex = months.findIndex((month) => month === normalized);
|
|
108
|
+
if (fullIndex !== -1) return fullIndex + 1;
|
|
109
|
+
const shortIndex = monthAbbreviations.findIndex((aliases) => aliases.some((alias) => alias === normalized));
|
|
110
|
+
return shortIndex === -1 ? void 0 : shortIndex + 1;
|
|
111
|
+
};
|
|
112
|
+
const relativeDirection = (value) => {
|
|
113
|
+
if (value === "last" || value === "previous") return -1;
|
|
114
|
+
if (value === "next" || value === "coming" || value === "upcoming") return 1;
|
|
115
|
+
return 0;
|
|
116
|
+
};
|
|
117
|
+
const relativeDirectionName = (direction) => {
|
|
118
|
+
if (direction < 0) return "last";
|
|
119
|
+
if (direction > 0) return "next";
|
|
120
|
+
return "this";
|
|
121
|
+
};
|
|
122
|
+
const quarterNumber = (value) => {
|
|
123
|
+
if (value.startsWith("q")) return Number(value.slice(1));
|
|
124
|
+
const index = quarterNames.findIndex((name) => name === value);
|
|
125
|
+
return index === -1 ? void 0 : index + 1;
|
|
126
|
+
};
|
|
127
|
+
const parseQuarter = (input) => {
|
|
128
|
+
const fixed = String$1.match(/^(q[1-4])(?: of)? (\d{4})$/u)(input);
|
|
129
|
+
const reversed = String$1.match(/^(\d{4}) (q[1-4])$/u)(input);
|
|
130
|
+
const namedFixed = String$1.match(/^(first|second|third|fourth) quarter(?: of)? (\d{4})$/u)(input);
|
|
131
|
+
const relative = String$1.match(/^(q[1-4])(?: of)? (last|this|next) year$/u)(input);
|
|
132
|
+
const namedRelative = String$1.match(/^(first|second|third|fourth) quarter(?: of)? (last|this|next) year$/u)(input);
|
|
133
|
+
const standalone = String$1.match(/^(q[1-4])$/u)(input);
|
|
134
|
+
const namedStandalone = String$1.match(/^(first|second|third|fourth) quarter$/u)(input);
|
|
135
|
+
const fixedMatch = Option.firstSomeOf([
|
|
136
|
+
fixed,
|
|
137
|
+
reversed,
|
|
138
|
+
namedFixed
|
|
139
|
+
]);
|
|
140
|
+
if (Option.isSome(fixedMatch)) {
|
|
141
|
+
const quarterText = textAt(fixedMatch.value, Option.isSome(reversed) ? 2 : 1);
|
|
142
|
+
const yearText = textAt(fixedMatch.value, Option.isSome(reversed) ? 1 : 2);
|
|
143
|
+
const quarter = quarterNumber(quarterText);
|
|
144
|
+
const year = validYear(yearText);
|
|
145
|
+
if (quarter !== void 0 && year !== void 0) return Option.some(fixedQuarterPeriod(year, quarter, `Q${quarter} ${year}`));
|
|
146
|
+
}
|
|
147
|
+
const relativeMatch = Option.firstSomeOf([relative, namedRelative]);
|
|
148
|
+
if (Option.isSome(relativeMatch)) {
|
|
149
|
+
const directionText = textAt(relativeMatch.value, 2);
|
|
150
|
+
const quarter = quarterNumber(textAt(relativeMatch.value, 1));
|
|
151
|
+
if (quarter !== void 0) {
|
|
152
|
+
const direction = relativeDirection(directionText);
|
|
153
|
+
return Option.some(quarterOfRelativeYear(quarter, direction, `Q${quarter} of ${directionText} year`));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const standaloneMatch = Option.firstSomeOf([standalone, namedStandalone]);
|
|
157
|
+
if (Option.isNone(standaloneMatch)) return Option.none();
|
|
158
|
+
const quarter = quarterNumber(textAt(standaloneMatch.value, 1));
|
|
159
|
+
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `Q${quarter}`));
|
|
160
|
+
};
|
|
161
|
+
const currentDateLabel = (day, month) => `${day} ${title(textAt(months, month - 1))}`;
|
|
162
|
+
const parseNamedDate = (input) => {
|
|
163
|
+
const dayFirst = String$1.match(/^(?:the )?([0-3]?\d)(?:st|nd|rd|th)?(?: of)? ([a-z]+\.?),? (\d{4})$/u)(input);
|
|
164
|
+
if (Option.isSome(dayFirst)) return namedDatePeriod(textAt(dayFirst.value, 3), textAt(dayFirst.value, 2), textAt(dayFirst.value, 1), monthNumber, (day, month, year) => `${day} ${title(textAt(months, month - 1))} ${year}`);
|
|
165
|
+
const monthFirst = String$1.match(/^([a-z]+\.?) (?:the )?([0-3]?\d)(?:st|nd|rd|th)?,? (\d{4})$/u)(input);
|
|
166
|
+
if (Option.isSome(monthFirst)) return namedDatePeriod(textAt(monthFirst.value, 3), textAt(monthFirst.value, 1), textAt(monthFirst.value, 2), monthNumber, (day, month, year) => `${day} ${title(textAt(months, month - 1))} ${year}`);
|
|
167
|
+
const currentDayFirst = String$1.match(/^(?:the )?([0-3]?\d)(?:st|nd|rd|th)?(?: of)? ([a-z]+\.?)$/u)(input);
|
|
168
|
+
const currentMonthFirst = String$1.match(/^([a-z]+\.?) (?:the )?([0-3]?\d)(?:st|nd|rd|th)?$/u)(input);
|
|
169
|
+
const current = Option.firstSomeOf([currentDayFirst, currentMonthFirst]);
|
|
170
|
+
if (Option.isNone(current)) return Option.none();
|
|
171
|
+
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
|
+
};
|
|
173
|
+
const parseBasePeriod = (input) => {
|
|
174
|
+
const absoluteDate = absoluteDatePeriod(input, "en");
|
|
175
|
+
if (Option.isSome(absoluteDate)) return absoluteDate;
|
|
176
|
+
const namedDate = parseNamedDate(input);
|
|
177
|
+
if (Option.isSome(namedDate)) return namedDate;
|
|
178
|
+
const quarter = parseQuarter(input);
|
|
179
|
+
if (Option.isSome(quarter)) return quarter;
|
|
180
|
+
const yearMatch = String$1.match(/^(?:(?:the )?(?:calendar )?year )?(\d{4})$/u)(input);
|
|
181
|
+
if (Option.isSome(yearMatch)) {
|
|
182
|
+
const year = validYear(textAt(yearMatch.value, 1));
|
|
183
|
+
if (year !== void 0) return Option.some(fixedYearPeriod(year, String(year)));
|
|
184
|
+
}
|
|
185
|
+
const monthYear = String$1.match(/^([a-z]+\.?) (\d{4})$/u)(input);
|
|
186
|
+
if (Option.isSome(monthYear)) {
|
|
187
|
+
const month = monthNumber(textAt(monthYear.value, 1));
|
|
188
|
+
const year = validYear(textAt(monthYear.value, 2));
|
|
189
|
+
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${title(textAt(months, month - 1))} ${year}`));
|
|
190
|
+
}
|
|
191
|
+
const relativeMonth = String$1.match(/^([a-z]+\.?) (?:of )?(last|this|next) year$/u)(input);
|
|
192
|
+
if (Option.isSome(relativeMonth)) {
|
|
193
|
+
const directionText = textAt(relativeMonth.value, 2);
|
|
194
|
+
const month = monthNumber(textAt(relativeMonth.value, 1));
|
|
195
|
+
const direction = relativeDirection(directionText);
|
|
196
|
+
if (month !== void 0) return Option.some(monthOfRelativeYear(month, direction, `${title(textAt(months, month - 1))} of ${directionText} year`));
|
|
197
|
+
}
|
|
198
|
+
const standaloneMonth = monthNumber(input);
|
|
199
|
+
if (standaloneMonth !== void 0) return Option.some(monthOfRelativeYear(standaloneMonth, 0, title(textAt(months, standaloneMonth - 1))));
|
|
200
|
+
if (input === "today") return Option.some(relativePeriod("day", 0, "today"));
|
|
201
|
+
if (input === "yesterday") return Option.some(relativePeriod("day", -1, "yesterday"));
|
|
202
|
+
if (input === "tomorrow") return Option.some(relativePeriod("day", 1, "tomorrow"));
|
|
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"));
|
|
208
|
+
const articlePeriod = String$1.match(/^the (day|week|month|quarter|year)$/u)(input);
|
|
209
|
+
if (Option.isSome(articlePeriod)) {
|
|
210
|
+
const entry = units.find((unit) => unit[0] === textAt(articlePeriod.value, 1));
|
|
211
|
+
if (entry !== void 0) return Option.some(relativePeriod(entry[1], 0, currentPeriod(entry[0])));
|
|
212
|
+
}
|
|
213
|
+
const outerRelative = String$1.match(/^(?:the )?(day|week|month|quarter|year) (before last|after next)$/u)(input);
|
|
214
|
+
if (Option.isSome(outerRelative)) {
|
|
215
|
+
const entry = units.find((unit) => unit[0] === textAt(outerRelative.value, 1));
|
|
216
|
+
if (entry !== void 0) {
|
|
217
|
+
const direction = textAt(outerRelative.value, 2) === "before last" ? -2 : 2;
|
|
218
|
+
return Option.some(relativePeriod(entry[1], direction, input));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const relative = String$1.match(/^(?:the )?(last|previous|this|current|next|coming|upcoming) (?:calendar )?([a-z]+)$/u)(input);
|
|
222
|
+
if (Option.isSome(relative)) {
|
|
223
|
+
const unitText = textAt(relative.value, 2);
|
|
224
|
+
const unit = units.find((entry) => entry[0] === unitText)?.[1];
|
|
225
|
+
if (unit !== void 0) {
|
|
226
|
+
const direction = relativeDirection(textAt(relative.value, 1));
|
|
227
|
+
const canonicalDirection = relativeDirectionName(direction);
|
|
228
|
+
return Option.some(relativePeriod(unit, direction, `${canonicalDirection} ${unitText}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return Option.none();
|
|
232
|
+
};
|
|
233
|
+
const parsePeriod = (input) => {
|
|
234
|
+
const previousDay = String$1.match(/^(?:the )?day before (.+)$/u)(input);
|
|
235
|
+
if (Option.isSome(previousDay)) {
|
|
236
|
+
const period = parseBasePeriod(textAt(previousDay.value, 1));
|
|
237
|
+
if (Option.isSome(period)) return Option.some(periodPreviousDay(period.value, `the day before ${period.value.canonical}`));
|
|
238
|
+
}
|
|
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
|
+
const wrapper = [
|
|
250
|
+
"during ",
|
|
251
|
+
"in ",
|
|
252
|
+
"for ",
|
|
253
|
+
"all of ",
|
|
254
|
+
"the whole of "
|
|
255
|
+
].find((prefix) => input.startsWith(prefix));
|
|
256
|
+
return parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
|
|
257
|
+
};
|
|
258
|
+
const boundaryCandidate = (input) => openBoundaryCandidate(input, [
|
|
259
|
+
["up to and including ", "through"],
|
|
260
|
+
["on or before ", "through"],
|
|
261
|
+
["on or after ", "since"],
|
|
262
|
+
["up to including ", "through"],
|
|
263
|
+
["since the beginning of ", "since"],
|
|
264
|
+
["since the start of ", "since"],
|
|
265
|
+
["from the beginning of ", "since"],
|
|
266
|
+
["from the start of ", "since"],
|
|
267
|
+
["starting from ", "since"],
|
|
268
|
+
["through the end of ", "through"],
|
|
269
|
+
["until the end of ", "through"],
|
|
270
|
+
["after the end of ", "after"],
|
|
271
|
+
["until the start of ", "before"],
|
|
272
|
+
["before the beginning of ", "before"],
|
|
273
|
+
["before the start of ", "before"],
|
|
274
|
+
["before beginning of ", "before"],
|
|
275
|
+
["before start of ", "before"],
|
|
276
|
+
["starting ", "since"],
|
|
277
|
+
["through ", "through"],
|
|
278
|
+
["earlier than ", "before"],
|
|
279
|
+
["later than ", "after"],
|
|
280
|
+
["before ", "before"],
|
|
281
|
+
["since ", "since"],
|
|
282
|
+
["after ", "after"],
|
|
283
|
+
["until ", "before"],
|
|
284
|
+
["till ", "before"],
|
|
285
|
+
["up to ", "before"],
|
|
286
|
+
["from ", "since"]
|
|
287
|
+
], parsePeriod);
|
|
288
|
+
const countedUnit = (value, amount) => units.find((entry) => value === (amount === 1 ? entry[0] : entry[2]));
|
|
289
|
+
const parseCalendarOffset = (input) => {
|
|
290
|
+
const singular = String$1.match(/^(?:(?:a|one) (day|week|month|quarter|year) (ago|prior|from now)|in (?:a|one) (day|week|month|quarter|year))$/u)(input);
|
|
291
|
+
if (Option.isSome(singular)) {
|
|
292
|
+
const unitText = textAt(singular.value, 1) || textAt(singular.value, 3);
|
|
293
|
+
const entry = units.find((unit) => unit[0] === unitText);
|
|
294
|
+
if (entry !== void 0) {
|
|
295
|
+
const directionText = textAt(singular.value, 2);
|
|
296
|
+
const isPast = directionText === "ago" || directionText === "prior";
|
|
297
|
+
const direction = isPast ? -1 : 1;
|
|
298
|
+
const canonical = isPast ? `1 ${entry[0]} ago` : `in 1 ${entry[0]}`;
|
|
299
|
+
return Option.some(candidate(periodRange(relativePeriod(entry[1], direction, canonical)), canonical));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const past = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) (?:ago|prior)$/u)(input);
|
|
303
|
+
const futureIn = String$1.match(/^in ([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years)$/u)(input);
|
|
304
|
+
const futureFromNow = String$1.match(/^([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years) from now$/u)(input);
|
|
305
|
+
const match = Option.firstSomeOf([
|
|
306
|
+
past,
|
|
307
|
+
futureIn,
|
|
308
|
+
futureFromNow
|
|
309
|
+
]);
|
|
310
|
+
if (Option.isNone(match)) return Option.none();
|
|
311
|
+
const amount = parseTrailingCount(textAt(match.value, 1));
|
|
312
|
+
if (Option.isNone(amount)) return Option.none();
|
|
313
|
+
const unitText = textAt(match.value, 2);
|
|
314
|
+
const entry = countedUnit(unitText, amount.value);
|
|
315
|
+
if (entry === void 0) return Option.none();
|
|
316
|
+
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
317
|
+
const canonical = direction < 0 ? `${amount.value} ${unitText} ago` : `in ${amount.value} ${unitText}`;
|
|
318
|
+
return Option.some(candidate(periodRange(relativePeriod(entry[1], direction, canonical)), canonical));
|
|
319
|
+
};
|
|
320
|
+
const parseRollingPeriod = (input) => {
|
|
321
|
+
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);
|
|
322
|
+
const singularPast = String$1.match(/^(?:past|the past|in the past|over the past|this past) (day|week|month|quarter|year)$/u)(input);
|
|
323
|
+
const future = String$1.match(/^(?:(?:next|coming|within) |(?:in|over|within) the (?:next|coming) )([1-9]\d*) (day|days|week|weeks|month|months|quarter|quarters|year|years)$/u)(input);
|
|
324
|
+
const singularFuture = String$1.match(/^(?:in|over|within) the (?:next|coming) (day|week|month|quarter|year)$/u)(input);
|
|
325
|
+
const match = Option.firstSomeOf([
|
|
326
|
+
past,
|
|
327
|
+
singularPast,
|
|
328
|
+
future,
|
|
329
|
+
singularFuture
|
|
330
|
+
]);
|
|
331
|
+
if (Option.isNone(match)) return Option.none();
|
|
332
|
+
const singular = Option.isSome(singularPast) || Option.isSome(singularFuture);
|
|
333
|
+
const amount = parseTrailingCount(singular ? "1" : textAt(match.value, 1));
|
|
334
|
+
if (Option.isNone(amount)) return Option.none();
|
|
335
|
+
const unitText = textAt(match.value, singular ? 1 : 2);
|
|
336
|
+
const entry = countedUnit(unitText, amount.value);
|
|
337
|
+
if (entry === void 0) return Option.none();
|
|
338
|
+
const isFuture = Option.isSome(future) || Option.isSome(singularFuture);
|
|
339
|
+
const range = isFuture ? futureRange(amount.value, entry[1]) : trailingRange(amount.value, entry[1]);
|
|
340
|
+
if (amount.value === 1) {
|
|
341
|
+
const canonical = isFuture ? `within the next ${entry[0]}` : `past ${entry[0]}`;
|
|
342
|
+
return Option.some(candidate(range, canonical));
|
|
343
|
+
}
|
|
344
|
+
const direction = isFuture ? "next" : "last";
|
|
345
|
+
return Option.some(candidate(range, `${direction} ${amount.value} ${entry[2]}`));
|
|
346
|
+
};
|
|
347
|
+
const parseElidedDateRange = (input) => {
|
|
348
|
+
const monthFirst = String$1.match(/^([a-z]+\.?) ([0-3]?\d)(?:st|nd|rd|th)?(?: (?:to|through) |[–—-])([0-3]?\d)(?:st|nd|rd|th)?,?(?: (\d{4}))?$/u)(input);
|
|
349
|
+
const dayFirst = String$1.match(/^([0-3]?\d)(?:st|nd|rd|th)?(?: (?:to|through) |[–—-])([0-3]?\d)(?:st|nd|rd|th)? ([a-z]+\.?)(?: (\d{4}))?$/u)(input);
|
|
350
|
+
const match = Option.firstSomeOf([monthFirst, dayFirst]);
|
|
351
|
+
if (Option.isNone(match)) return Option.none();
|
|
352
|
+
const isMonthFirst = Option.isSome(monthFirst);
|
|
353
|
+
const month = textAt(match.value, isMonthFirst ? 1 : 3);
|
|
354
|
+
const lowerDay = textAt(match.value, isMonthFirst ? 2 : 1);
|
|
355
|
+
const upperDay = textAt(match.value, isMonthFirst ? 3 : 2);
|
|
356
|
+
const year = textAt(match.value, 4);
|
|
357
|
+
const suffix = year.length === 0 ? "" : ` ${year}`;
|
|
358
|
+
return joinedPeriodCandidate(`from ${lowerDay} ${month}${suffix} to ${upperDay} ${month}${suffix}`, [["from ", " to "]], parsePeriod, (lower, upper) => `from ${lower} to ${upper}`);
|
|
359
|
+
};
|
|
360
|
+
const parseEnglish = (input) => {
|
|
361
|
+
const remaining = remainingPeriodPhrases.find((entry) => entry.phrase === input);
|
|
362
|
+
if (remaining !== void 0) return Option.some(candidate(remainingPeriodRange(remaining.entry[1]), `rest of the ${remaining.entry[0]}`));
|
|
363
|
+
if ([
|
|
364
|
+
"until now",
|
|
365
|
+
"till now",
|
|
366
|
+
"up to now",
|
|
367
|
+
"through now"
|
|
368
|
+
].includes(input)) return Option.some(candidate(untilNowRange(), "until now"));
|
|
369
|
+
if ([
|
|
370
|
+
"from now",
|
|
371
|
+
"from now on",
|
|
372
|
+
"starting now"
|
|
373
|
+
].includes(input)) return Option.some(candidate(fromNowRange(), "from now"));
|
|
374
|
+
const offset = parseCalendarOffset(input);
|
|
375
|
+
if (Option.isSome(offset)) return offset;
|
|
376
|
+
const rolling = parseRollingPeriod(input);
|
|
377
|
+
if (Option.isSome(rolling)) return rolling;
|
|
378
|
+
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
379
|
+
if (toDate !== void 0) return Option.some(candidate(periodToDateRange(toDate.entry[1]), `${toDate.entry[0]} to date`));
|
|
380
|
+
const elided = parseElidedDateRange(input);
|
|
381
|
+
if (Option.isSome(elided)) return elided;
|
|
382
|
+
const nowBounded = joinedNowCandidate(input, [
|
|
383
|
+
["from ", " to now"],
|
|
384
|
+
["from ", " until now"],
|
|
385
|
+
["from ", " through now"],
|
|
386
|
+
["from ", " to date"],
|
|
387
|
+
["between ", " and now"]
|
|
388
|
+
], [
|
|
389
|
+
"from now to ",
|
|
390
|
+
"from now until ",
|
|
391
|
+
"from now through ",
|
|
392
|
+
"between now and "
|
|
393
|
+
], parsePeriod, (period) => `from ${period} to now`, (period) => `from now to ${period}`);
|
|
394
|
+
if (Option.isSome(nowBounded)) return nowBounded;
|
|
395
|
+
const bounded = joinedPeriodCandidate(input, [
|
|
396
|
+
["from ", " to and including "],
|
|
397
|
+
["from ", " through and including "],
|
|
398
|
+
["from ", " to "],
|
|
399
|
+
["from ", " until "],
|
|
400
|
+
["from ", " through "],
|
|
401
|
+
["from ", " till "],
|
|
402
|
+
["between ", " and "],
|
|
403
|
+
["between ", " through "],
|
|
404
|
+
["", " to and including "],
|
|
405
|
+
["", " through and including "],
|
|
406
|
+
["", " to "],
|
|
407
|
+
["", " until "],
|
|
408
|
+
["", " through "],
|
|
409
|
+
["", " till "],
|
|
410
|
+
["", " - "],
|
|
411
|
+
["", " – "],
|
|
412
|
+
["", " — "],
|
|
413
|
+
["between ", "-"],
|
|
414
|
+
["", "-"],
|
|
415
|
+
["between ", "–"],
|
|
416
|
+
["", "–"],
|
|
417
|
+
["between ", "—"],
|
|
418
|
+
["", "—"],
|
|
419
|
+
["between ", "~"],
|
|
420
|
+
["", "~"]
|
|
421
|
+
], parsePeriod, (lower, upper) => `from ${lower} to ${upper}`);
|
|
422
|
+
if (Option.isSome(bounded)) return bounded;
|
|
423
|
+
const boundary = boundaryCandidate(input);
|
|
424
|
+
if (Option.isSome(boundary)) return boundary;
|
|
425
|
+
return parsePeriod(input).pipe(Option.map((period) => candidate(periodRange(period), period.canonical)));
|
|
426
|
+
};
|
|
427
|
+
const staticPeriodPhrases = [
|
|
428
|
+
"today",
|
|
429
|
+
"yesterday",
|
|
430
|
+
"the day before yesterday",
|
|
431
|
+
"tomorrow",
|
|
432
|
+
"this weekend",
|
|
433
|
+
"last weekend",
|
|
434
|
+
"next weekend",
|
|
435
|
+
"the weekend before last",
|
|
436
|
+
"the weekend after next",
|
|
437
|
+
...units.filter((entry) => entry[1] !== "day").flatMap((entry) => [
|
|
438
|
+
`this ${entry[0]}`,
|
|
439
|
+
`last ${entry[0]}`,
|
|
440
|
+
`next ${entry[0]}`,
|
|
441
|
+
`start of this ${entry[0]}`,
|
|
442
|
+
`end of this ${entry[0]}`,
|
|
443
|
+
`start of last ${entry[0]}`,
|
|
444
|
+
`end of last ${entry[0]}`,
|
|
445
|
+
`start of next ${entry[0]}`,
|
|
446
|
+
`end of next ${entry[0]}`
|
|
447
|
+
]),
|
|
448
|
+
...[
|
|
449
|
+
1,
|
|
450
|
+
2,
|
|
451
|
+
3,
|
|
452
|
+
4
|
|
453
|
+
].flatMap((quarter) => [
|
|
454
|
+
`q${quarter}`,
|
|
455
|
+
`q${quarter} of last year`,
|
|
456
|
+
`q${quarter} of next year`
|
|
457
|
+
]),
|
|
458
|
+
...months.flatMap((month) => [
|
|
459
|
+
month,
|
|
460
|
+
`${month} of last year`,
|
|
461
|
+
`${month} of next year`
|
|
462
|
+
])
|
|
463
|
+
];
|
|
464
|
+
const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
|
|
465
|
+
const boundaryPrefixes = [
|
|
466
|
+
"since ",
|
|
467
|
+
"before ",
|
|
468
|
+
"through ",
|
|
469
|
+
"after "
|
|
470
|
+
];
|
|
471
|
+
const countedSuggestions = (input) => {
|
|
472
|
+
const amount = naturalCount(input);
|
|
473
|
+
if (amount === void 0) return [];
|
|
474
|
+
return units.flatMap((entry) => {
|
|
475
|
+
const noun = amount === 1 ? entry[0] : entry[2];
|
|
476
|
+
return [
|
|
477
|
+
`last ${amount} ${noun}`,
|
|
478
|
+
`${amount} ${noun}`,
|
|
479
|
+
`next ${amount} ${noun}`,
|
|
480
|
+
`${amount} ${noun} ago`,
|
|
481
|
+
`in ${amount} ${noun}`
|
|
482
|
+
];
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
const englishSuggestionPhrases = [
|
|
486
|
+
...units.map((entry) => `${entry[0]} to date`),
|
|
487
|
+
...remainingPeriodPhrases.map((entry) => entry.phrase),
|
|
488
|
+
...units.flatMap((entry) => [`past ${entry[0]}`, `within the next ${entry[0]}`]),
|
|
489
|
+
...staticPeriodPhrases,
|
|
490
|
+
...prefixNaturalPhrases(staticPeriodPhrases, boundaryPrefixes),
|
|
491
|
+
"until now",
|
|
492
|
+
"from now"
|
|
493
|
+
];
|
|
494
|
+
const suggestEnglish = (input, limit) => {
|
|
495
|
+
const fixed = fixedCalendarPeriodPhrases(input, months);
|
|
496
|
+
return completeNaturalPhrases(input, [
|
|
497
|
+
...englishSuggestionPhrases,
|
|
498
|
+
...fixed,
|
|
499
|
+
...prefixNaturalPhrases(fixed, boundaryPrefixes),
|
|
500
|
+
...countedSuggestions(input)
|
|
501
|
+
], limit);
|
|
502
|
+
};
|
|
503
|
+
const renderEnglish = (range) => {
|
|
504
|
+
const offset = calendarPeriodOffset(range);
|
|
505
|
+
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
506
|
+
const entry = units.find((unit) => unit[1] === offset.value.unit);
|
|
507
|
+
if (entry !== void 0) {
|
|
508
|
+
const noun = offset.value.amount === -1 || offset.value.amount === 1 ? entry[0] : entry[2];
|
|
509
|
+
return Option.some(offset.value.amount < 0 ? `${-offset.value.amount} ${noun} ago` : `in ${offset.value.amount} ${noun}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const future = futurePeriod(range);
|
|
513
|
+
if (Option.isSome(future)) {
|
|
514
|
+
const entry = units.find((unit) => unit[1] === future.value.unit);
|
|
515
|
+
if (entry !== void 0) return Option.some(future.value.amount === 1 ? `within the next ${entry[0]}` : `next ${future.value.amount} ${entry[2]}`);
|
|
516
|
+
}
|
|
517
|
+
const trailing = trailingPeriod(range);
|
|
518
|
+
if (Option.isSome(trailing)) {
|
|
519
|
+
const entry = units.find((unit) => unit[1] === trailing.value.unit);
|
|
520
|
+
if (entry !== void 0) return Option.some(trailing.value.amount === 1 ? `past ${entry[0]}` : `last ${trailing.value.amount} ${entry[2]}`);
|
|
521
|
+
}
|
|
522
|
+
const periods = [
|
|
523
|
+
...staticPeriods,
|
|
524
|
+
...currentYearDatePeriods(range, currentDateLabel),
|
|
525
|
+
...periodsFromPhrases([...datedPeriods(range, months), ...datedQuarterPeriods(range)], parsePeriod)
|
|
526
|
+
];
|
|
527
|
+
return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry[1]), `${entry[0]} to date`)), ...units.map((entry) => candidate(remainingPeriodRange(entry[1]), `rest of the ${entry[0]}`))], periods, (period) => `since ${period}`, (period) => `before ${period}`, (period) => `through ${period}`, (period) => `after ${period}`, (lower, upper) => `from ${lower} to ${upper}`, (period) => `from ${period} to now`, (period) => `from now to ${period}`, () => "until now", () => "from now");
|
|
528
|
+
};
|
|
529
|
+
const EnglishContribution = new BaseLanguageContribution({
|
|
530
|
+
locale: "en",
|
|
531
|
+
vocabulary: [
|
|
532
|
+
...months,
|
|
533
|
+
...quarterNames,
|
|
534
|
+
"q1",
|
|
535
|
+
"q2",
|
|
536
|
+
"q3",
|
|
537
|
+
"q4",
|
|
538
|
+
...monthAbbreviations.flatMap((aliases) => aliases),
|
|
539
|
+
...toDatePhrases.flatMap((entry) => entry.phrase.split(" ")),
|
|
540
|
+
...remainingPeriodPhrases.flatMap((entry) => entry.phrase.split(" ")),
|
|
541
|
+
...units.flatMap((entry) => [entry[0], entry[2]]),
|
|
542
|
+
"after",
|
|
543
|
+
"ago",
|
|
544
|
+
"all",
|
|
545
|
+
"one",
|
|
546
|
+
"and",
|
|
547
|
+
"beginning",
|
|
548
|
+
"before",
|
|
549
|
+
"calendar",
|
|
550
|
+
"coming",
|
|
551
|
+
"current",
|
|
552
|
+
"date",
|
|
553
|
+
"during",
|
|
554
|
+
"end",
|
|
555
|
+
"far",
|
|
556
|
+
"for",
|
|
557
|
+
"from",
|
|
558
|
+
"in",
|
|
559
|
+
"including",
|
|
560
|
+
"last",
|
|
561
|
+
"next",
|
|
562
|
+
"now",
|
|
563
|
+
"on",
|
|
564
|
+
"over",
|
|
565
|
+
"of",
|
|
566
|
+
"past",
|
|
567
|
+
"previous",
|
|
568
|
+
"prior",
|
|
569
|
+
"since",
|
|
570
|
+
"so",
|
|
571
|
+
"start",
|
|
572
|
+
"starting",
|
|
573
|
+
"this",
|
|
574
|
+
"through",
|
|
575
|
+
"till",
|
|
576
|
+
"whole",
|
|
577
|
+
"to",
|
|
578
|
+
"until",
|
|
579
|
+
"upcoming",
|
|
580
|
+
"within",
|
|
581
|
+
"today",
|
|
582
|
+
"tomorrow",
|
|
583
|
+
"weekend",
|
|
584
|
+
"yesterday"
|
|
585
|
+
],
|
|
586
|
+
normalize: normalizeNaturalText,
|
|
587
|
+
correct: correctWhitespaceSeparatedText,
|
|
588
|
+
parseExact: parseEnglish,
|
|
589
|
+
suggest: suggestEnglish,
|
|
590
|
+
render: renderEnglish
|
|
591
|
+
});
|
|
592
|
+
const EnglishLanguage = defineLanguagePlugin({
|
|
593
|
+
id: "chronolizer/language-en",
|
|
594
|
+
effect: (context) => Effect.asVoid(context.register("chronolizer/language-en", EnglishContribution))
|
|
595
|
+
});
|
|
596
|
+
const EnglishLanguageLayer = languagePluginsLayer([EnglishLanguage]);
|
|
597
|
+
//#endregion
|
|
598
|
+
export { EnglishContribution, EnglishLanguage, EnglishLanguageLayer };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LanguageConflictError, LanguageRegistrationError } from "../language/errors.mjs";
|
|
2
|
+
import { BaseLanguageContribution, LanguagePlugin } from "../language/model.mjs";
|
|
3
|
+
import { LanguageRegistry } from "../language/registry.mjs";
|
|
4
|
+
import "../index.mjs";
|
|
5
|
+
//#region src/locales/es.d.ts
|
|
6
|
+
declare const SpanishContribution: BaseLanguageContribution;
|
|
7
|
+
declare const SpanishLanguage: LanguagePlugin;
|
|
8
|
+
declare const SpanishLanguageLayer: import("effect/Layer").Layer<LanguageRegistry, LanguageConflictError | LanguageRegistrationError, never>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { SpanishContribution, SpanishLanguage, SpanishLanguageLayer };
|