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.
- package/README.md +50 -47
- package/dist/ast/fold.mjs +8 -10
- package/dist/ast/normalize.mjs +7 -16
- package/dist/filter/codec.mjs +5 -6
- package/dist/filter/expression.mjs +26 -21
- package/dist/language/model.mjs +1 -1
- package/dist/language/registry.mjs +34 -12
- package/dist/locales/cs.mjs +194 -36
- package/dist/locales/de.mjs +307 -490
- package/dist/locales/en.mjs +162 -46
- package/dist/locales/es.mjs +187 -37
- package/dist/locales/fr.mjs +168 -52
- package/dist/locales/nl.mjs +125 -37
- package/dist/locales/pl.mjs +163 -36
- package/dist/locales/shared.mjs +68 -5
- package/dist/locales/tr.mjs +144 -42
- package/dist/natural/correction.d.mts +1 -1
- package/dist/natural/correction.mjs +97 -28
- package/dist/natural/format.mjs +1 -1
- package/dist/natural/parse.mjs +6 -5
- package/dist/natural/suggest.mjs +27 -3
- package/dist/natural/suggestion.mjs +50 -26
- package/dist/resolve/resolve.mjs +2 -3
- package/package.json +2 -1
package/dist/locales/fr.mjs
CHANGED
|
@@ -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,
|
|
6
|
+
import { absoluteDatePeriod, calendarPeriodOffset, candidate, compileCountAliasNormalizer, countAliasVocabulary, currentYearDatePeriods, datedPeriods, datedQuarterPeriods, decomposeShiftedPeriodRange, fixedMonthPeriod, fixedQuarterPeriod, fixedYearPeriod, fromNowRange, futurePeriod, futureRange, joinedNowCandidate, joinedPeriodCandidate, monthOfRelativeYear, namedCurrentYearDatePeriod, namedDatePeriod, openBoundaryCandidate, parseTrailingCount, periodBoundaryCandidate, periodDay, 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/fr.ts
|
|
10
9
|
const months = [
|
|
@@ -21,6 +20,90 @@ const months = [
|
|
|
21
20
|
"novembre",
|
|
22
21
|
"décembre"
|
|
23
22
|
];
|
|
23
|
+
const weekdays = [
|
|
24
|
+
"lundi",
|
|
25
|
+
"mardi",
|
|
26
|
+
"mercredi",
|
|
27
|
+
"jeudi",
|
|
28
|
+
"vendredi",
|
|
29
|
+
"samedi",
|
|
30
|
+
"dimanche"
|
|
31
|
+
];
|
|
32
|
+
const nextWeekdayPhrases = weekdays.map((weekday) => `${weekday} prochain`);
|
|
33
|
+
const frenchCountWords = [
|
|
34
|
+
["deux"],
|
|
35
|
+
["trois"],
|
|
36
|
+
["quatre"],
|
|
37
|
+
["cinq"],
|
|
38
|
+
["six"],
|
|
39
|
+
["sept"],
|
|
40
|
+
["huit"],
|
|
41
|
+
["neuf"],
|
|
42
|
+
["dix"],
|
|
43
|
+
["onze"],
|
|
44
|
+
["douze"],
|
|
45
|
+
["treize"],
|
|
46
|
+
["quatorze"],
|
|
47
|
+
["quinze"],
|
|
48
|
+
["seize"],
|
|
49
|
+
["dix-sept"],
|
|
50
|
+
["dix-huit"],
|
|
51
|
+
["dix-neuf"],
|
|
52
|
+
["vingt"]
|
|
53
|
+
];
|
|
54
|
+
const frenchCompoundCountAliases = () => {
|
|
55
|
+
const words = [
|
|
56
|
+
"",
|
|
57
|
+
"un",
|
|
58
|
+
"deux",
|
|
59
|
+
"trois",
|
|
60
|
+
"quatre",
|
|
61
|
+
"cinq",
|
|
62
|
+
"six",
|
|
63
|
+
"sept",
|
|
64
|
+
"huit",
|
|
65
|
+
"neuf",
|
|
66
|
+
"dix",
|
|
67
|
+
"onze",
|
|
68
|
+
"douze",
|
|
69
|
+
"treize",
|
|
70
|
+
"quatorze",
|
|
71
|
+
"quinze",
|
|
72
|
+
"seize",
|
|
73
|
+
"dix-sept",
|
|
74
|
+
"dix-huit",
|
|
75
|
+
"dix-neuf"
|
|
76
|
+
];
|
|
77
|
+
const aliases = [];
|
|
78
|
+
for (let amount = 21; amount <= 99; amount += 1) {
|
|
79
|
+
const tens = Math.floor(amount / 10);
|
|
80
|
+
const remainder = amount % 10;
|
|
81
|
+
let phrase = "";
|
|
82
|
+
if (tens <= 6) {
|
|
83
|
+
const tensWord = [
|
|
84
|
+
"",
|
|
85
|
+
"",
|
|
86
|
+
"vingt",
|
|
87
|
+
"trente",
|
|
88
|
+
"quarante",
|
|
89
|
+
"cinquante",
|
|
90
|
+
"soixante"
|
|
91
|
+
][tens];
|
|
92
|
+
if (tensWord === void 0) continue;
|
|
93
|
+
if (remainder === 0) phrase = tensWord;
|
|
94
|
+
else phrase = remainder === 1 ? `${tensWord} et un` : `${tensWord}-${words[remainder] ?? ""}`;
|
|
95
|
+
} else if (tens === 7) phrase = remainder === 1 ? "soixante et onze" : `soixante-${words[10 + remainder] ?? ""}`;
|
|
96
|
+
else if (tens === 8) phrase = remainder === 0 ? "quatre-vingts" : `quatre-vingt-${words[remainder] ?? ""}`;
|
|
97
|
+
else phrase = `quatre-vingt-${words[10 + remainder] ?? ""}`;
|
|
98
|
+
aliases.push([phrase, amount], [phrase.replaceAll("-", " "), amount]);
|
|
99
|
+
if (amount % 10 === 1) aliases.push([phrase.replace(/un$/u, "une"), amount]);
|
|
100
|
+
}
|
|
101
|
+
return aliases;
|
|
102
|
+
};
|
|
103
|
+
const frenchCountAliases = [...sequentialCountAliases(frenchCountWords, 2), ...frenchCompoundCountAliases()];
|
|
104
|
+
const normalizeFrenchCounts = compileCountAliasNormalizer(frenchCountAliases);
|
|
105
|
+
const frenchCountVocabulary = new Set(countAliasVocabulary(frenchCountAliases));
|
|
106
|
+
const correctFrench = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, frenchCountVocabulary);
|
|
24
107
|
const monthAbbreviations = [
|
|
25
108
|
["jan", "janv"],
|
|
26
109
|
[
|
|
@@ -416,15 +499,7 @@ const parseNamedDate = (input) => {
|
|
|
416
499
|
const named = String$1.match(/^(?:le )?(1er|[0-3]?\d)(?: de)? ([a-zàâçéèêëîïôûùüÿœ]+\.?),?(?: de)? (\d{4})$/u)(input);
|
|
417
500
|
if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1).replace(/er$/u, ""), monthNumber, dateLabel);
|
|
418
501
|
const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
|
|
419
|
-
if (Option.isSome(numeric))
|
|
420
|
-
const year = validYear(textAt(numeric.value, 3));
|
|
421
|
-
const month = Number(textAt(numeric.value, 2));
|
|
422
|
-
const day = Number(textAt(numeric.value, 1));
|
|
423
|
-
if (year !== void 0 && month >= 1 && month <= 12) {
|
|
424
|
-
const value = isoDate(year, month, day);
|
|
425
|
-
if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
|
|
426
|
-
}
|
|
427
|
-
}
|
|
502
|
+
if (Option.isSome(numeric)) return namedDatePeriod(textAt(numeric.value, 3), textAt(numeric.value, 2), textAt(numeric.value, 1), Number, dateLabel);
|
|
428
503
|
const current = String$1.match(/^(?:le )?(1er|[0-3]?\d)(?: de)? ([a-zàâçéèêëîïôûùüÿœ]+\.?)$/u)(input);
|
|
429
504
|
if (Option.isSome(current)) return namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1).replace(/er$/u, ""), monthNumber, currentDateLabel);
|
|
430
505
|
const relative = String$1.match(/^(?:le )?(1er|[0-3]?\d) (de .+|du .+)$/u)(input);
|
|
@@ -461,13 +536,13 @@ const parseQuarter = (input) => {
|
|
|
461
536
|
const quarter = quarterNumber(textAt(standalone.value, 1));
|
|
462
537
|
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `T${quarter}`));
|
|
463
538
|
};
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (Option.isSome(
|
|
539
|
+
const parseDatedPeriod = (input) => {
|
|
540
|
+
const knownPeriod = Option.firstSomeOf([
|
|
541
|
+
absoluteDatePeriod(input, "fr"),
|
|
542
|
+
parseNamedDate(input),
|
|
543
|
+
parseQuarter(input)
|
|
544
|
+
]);
|
|
545
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
471
546
|
const yearMatch = String$1.match(/^(?:(?:l')?année |annee )?(\d{4})$/u)(input);
|
|
472
547
|
if (Option.isSome(yearMatch)) {
|
|
473
548
|
const year = validYear(textAt(yearMatch.value, 1));
|
|
@@ -479,6 +554,20 @@ const parseBasePeriod = (input) => {
|
|
|
479
554
|
const year = validYear(textAt(monthYear.value, 2));
|
|
480
555
|
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
|
|
481
556
|
}
|
|
557
|
+
return Option.none();
|
|
558
|
+
};
|
|
559
|
+
const parseBasePeriod = (input) => {
|
|
560
|
+
const datedPeriod = parseDatedPeriod(input);
|
|
561
|
+
if (Option.isSome(datedPeriod)) return datedPeriod;
|
|
562
|
+
const suffixedRelativeMonth = String$1.match(/^([a-zàâçéèêëîïôûùüÿœ]+\.?) (dernier|prochain)$/u)(input);
|
|
563
|
+
if (Option.isSome(suffixedRelativeMonth)) {
|
|
564
|
+
const month = monthNumber(textAt(suffixedRelativeMonth.value, 1));
|
|
565
|
+
if (month !== void 0) {
|
|
566
|
+
const modifier = textAt(suffixedRelativeMonth.value, 2);
|
|
567
|
+
const direction = modifier === "dernier" ? -1 : 1;
|
|
568
|
+
return Option.some(monthOfRelativeYear(month, direction, `${textAt(months, month - 1)} ${modifier}`));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
482
571
|
const relativeMonth = String$1.match(/^([a-zàâçéèêëîïôûùüÿœ]+\.?) de (l'année dernière|l'annee derniere|l'année prochaine|l'annee prochaine|cette année|cette annee)$/u)(input);
|
|
483
572
|
if (Option.isSome(relativeMonth)) {
|
|
484
573
|
const month = monthNumber(textAt(relativeMonth.value, 1));
|
|
@@ -509,13 +598,32 @@ const parseBasePeriod = (input) => {
|
|
|
509
598
|
].includes(input)) return Option.some(relativeWeekend(1, "le week-end prochain"));
|
|
510
599
|
if (input === "l'avant-dernier week-end") return Option.some(relativeWeekend(-2, input));
|
|
511
600
|
if (input === "le week-end après le prochain") return Option.some(relativeWeekend(2, input));
|
|
512
|
-
|
|
601
|
+
const weekday = nextWeekdayPhrases.indexOf(input);
|
|
602
|
+
return weekday === -1 ? Option.none() : Option.some(relativeWeekday(weekday, 1, nextWeekdayPhrases[weekday] ?? input));
|
|
513
603
|
};
|
|
514
604
|
const parsePeriod = (input) => {
|
|
605
|
+
const shifted = String$1.match(/^(.+) (il y a|dans) ([1-9]\d*) (jour|jours|semaine|semaines|mois|trimestre|trimestres|an|ans|année|annee|années|annees)$/u)(input);
|
|
606
|
+
if (Option.isSome(shifted)) {
|
|
607
|
+
const amount = parseTrailingCount(textAt(shifted.value, 3));
|
|
608
|
+
const alias = unitAliases.find((unit) => unit[0] === textAt(shifted.value, 4));
|
|
609
|
+
const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
|
|
610
|
+
const period = parsePeriod(textAt(shifted.value, 1));
|
|
611
|
+
if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
|
|
612
|
+
const past = textAt(shifted.value, 2) === "il y a";
|
|
613
|
+
const direction = past ? -amount.value : amount.value;
|
|
614
|
+
const noun = amount.value === 1 ? entry.singular : entry.plural;
|
|
615
|
+
const canonical = `${period.value.canonical} ${past ? "il y a" : "dans"} ${amount.value} ${noun}`;
|
|
616
|
+
return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
515
619
|
const edge = String$1.match(/^(?:le |la |l')?(début|debut|commencement|fin)(?: du | de la | de l'| de )(.+)$/u)(input);
|
|
516
620
|
if (Option.isSome(edge)) {
|
|
517
621
|
const edgeName = textAt(edge.value, 1);
|
|
518
|
-
const
|
|
622
|
+
const periodText = textAt(edge.value, 2);
|
|
623
|
+
const basePeriod = parseBasePeriod(periodText);
|
|
624
|
+
const implicitUnit = unitAliases.find((entry) => entry[0] === periodText)?.[1];
|
|
625
|
+
const implicit = units.find((entry) => entry.unit === implicitUnit);
|
|
626
|
+
const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
|
|
519
627
|
if (Option.isSome(period)) {
|
|
520
628
|
const isEnd = edgeName === "fin";
|
|
521
629
|
const canonical = `${isEnd ? "fin" : "début"} ${withDe(period.value.canonical)}`;
|
|
@@ -529,7 +637,8 @@ const parsePeriod = (input) => {
|
|
|
529
637
|
"tout le ",
|
|
530
638
|
"toute la "
|
|
531
639
|
].find((prefix) => input.startsWith(prefix));
|
|
532
|
-
|
|
640
|
+
const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
|
|
641
|
+
return Option.isSome(base) ? base : parseCalendarOffset(input);
|
|
533
642
|
};
|
|
534
643
|
const countedUnit = (value, amount) => {
|
|
535
644
|
const plural = value === "mois" || value.endsWith("s");
|
|
@@ -537,36 +646,29 @@ const countedUnit = (value, amount) => {
|
|
|
537
646
|
const unit = unitAliases.find((entry) => entry[0] === value)?.[1];
|
|
538
647
|
return unit === void 0 ? void 0 : units.find((entry) => entry.unit === unit);
|
|
539
648
|
};
|
|
649
|
+
const rollingPastModifierPattern = "derni(?:ers|ères|eres)|pass(?:és|ées|es)|pr[eé]c[eé]dent(?:s|es)";
|
|
650
|
+
const rollingFutureModifierPattern = "prochain(?:s|es)|suivant(?:s|es)";
|
|
651
|
+
const rollingModifierMatchPattern = new RegExp(`(?:^| )(${`${rollingPastModifierPattern}|${rollingFutureModifierPattern}`})(?: |$)`, "u");
|
|
540
652
|
const modifierAgreesWithNoun = (input, noun) => {
|
|
541
|
-
const match = String$1.match(
|
|
653
|
+
const match = String$1.match(rollingModifierMatchPattern)(input);
|
|
542
654
|
if (Option.isNone(match)) return true;
|
|
543
655
|
const modifier = textAt(match.value, 1);
|
|
544
|
-
return (noun.startsWith("semaine") || noun.startsWith("année") || noun.startsWith("annee")) ===
|
|
545
|
-
"dernières",
|
|
546
|
-
"dernieres",
|
|
547
|
-
"passées",
|
|
548
|
-
"passes",
|
|
549
|
-
"précédentes",
|
|
550
|
-
"precedentes",
|
|
551
|
-
"prochaines",
|
|
552
|
-
"suivantes"
|
|
553
|
-
].includes(modifier);
|
|
656
|
+
return (noun.startsWith("semaine") || noun.startsWith("année") || noun.startsWith("annee")) === modifier.endsWith("es");
|
|
554
657
|
};
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const rollingBarePattern = compileCountedPattern("^([1-9]\\d*) (UNIT)$");
|
|
658
|
+
const compilePattern = (source) => new RegExp(source, "u");
|
|
659
|
+
const calendarPastPattern = /^il y a ([1-9]\d*) ([^ ]+)$/u;
|
|
660
|
+
const calendarFuturePattern = /^dans ([1-9]\d*) ([^ ]+)$/u;
|
|
661
|
+
const rollingSincePattern = /^depuis ([1-9]\d*) ([^ ]+)$/u;
|
|
662
|
+
const rollingBarePattern = /^([1-9]\d*) ([^ ]+)$/u;
|
|
561
663
|
const rollingPastPatterns = [
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
664
|
+
compilePattern(`^(?:(?:les|la) )?(?:${rollingPastModifierPattern}) ([1-9]\\d*) ([^ ]+)$`),
|
|
665
|
+
compilePattern(`^(?:(?:les|la|au cours des|pendant les) )?([1-9]\\d*) (?:${rollingPastModifierPattern}) ([^ ]+)$`),
|
|
666
|
+
compilePattern(`^([1-9]\\d*) ([^ ]+) (?:${rollingPastModifierPattern})$`)
|
|
565
667
|
];
|
|
566
668
|
const rollingFuturePatterns = [
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
669
|
+
compilePattern(`^(?:(?:les|la) )?(?:${rollingFutureModifierPattern}) ([1-9]\\d*) ([^ ]+)$`),
|
|
670
|
+
compilePattern(`^(?:(?:les|la|au cours des|pendant les) )?([1-9]\\d*) (?:${rollingFutureModifierPattern}) ([^ ]+)$`),
|
|
671
|
+
compilePattern(`^([1-9]\\d*) ([^ ]+) (?:${rollingFutureModifierPattern}|à venir)$`)
|
|
570
672
|
];
|
|
571
673
|
const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
|
|
572
674
|
const relativeCountPhrase = (amount, entry, future) => {
|
|
@@ -623,7 +725,7 @@ const singularCalendarOffsets = units.flatMap((entry) => {
|
|
|
623
725
|
});
|
|
624
726
|
const parseCalendarOffset = (input) => {
|
|
625
727
|
const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
|
|
626
|
-
if (singular !== void 0) return Option.some(
|
|
728
|
+
if (singular !== void 0) return Option.some(relativePeriod(singular.entry.unit, singular.direction, singular.phrase));
|
|
627
729
|
const past = String$1.match(calendarPastPattern)(input);
|
|
628
730
|
const future = String$1.match(calendarFuturePattern)(input);
|
|
629
731
|
const match = Option.firstSomeOf([past, future]);
|
|
@@ -635,7 +737,7 @@ const parseCalendarOffset = (input) => {
|
|
|
635
737
|
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
636
738
|
const noun = amount.value === 1 ? entry.singular : entry.plural;
|
|
637
739
|
const canonical = direction < 0 ? `il y a ${amount.value} ${noun}` : `dans ${amount.value} ${noun}`;
|
|
638
|
-
return Option.some(
|
|
740
|
+
return Option.some(relativePeriod(entry.unit, direction, canonical));
|
|
639
741
|
};
|
|
640
742
|
const parseRollingPeriod = (input) => {
|
|
641
743
|
const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
|
|
@@ -708,8 +810,6 @@ const parseFrench = (input) => {
|
|
|
708
810
|
"à partir de maintenant",
|
|
709
811
|
"désormais"
|
|
710
812
|
].includes(input)) return Option.some(candidate(fromNowRange(), "depuis maintenant"));
|
|
711
|
-
const offset = parseCalendarOffset(input);
|
|
712
|
-
if (Option.isSome(offset)) return offset;
|
|
713
813
|
const rolling = parseRollingPeriod(input);
|
|
714
814
|
if (Option.isSome(rolling)) return rolling;
|
|
715
815
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
@@ -723,7 +823,8 @@ const parseFrench = (input) => {
|
|
|
723
823
|
], [
|
|
724
824
|
"d'aujourd'hui à ",
|
|
725
825
|
"de maintenant à ",
|
|
726
|
-
"entre aujourd'hui et "
|
|
826
|
+
"entre aujourd'hui et ",
|
|
827
|
+
"maintenant à "
|
|
727
828
|
], parsePeriod, (period) => `depuis ${period} jusqu'à maintenant`, (period) => `de maintenant à ${period}`);
|
|
728
829
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
729
830
|
const bounded = joinedPeriodCandidate(input, [
|
|
@@ -775,9 +876,12 @@ const staticPeriodPhrases = [
|
|
|
775
876
|
]),
|
|
776
877
|
...months.flatMap((month) => [
|
|
777
878
|
month,
|
|
879
|
+
`${month} dernier`,
|
|
880
|
+
`${month} prochain`,
|
|
778
881
|
`${month} de l'année dernière`,
|
|
779
882
|
`${month} de l'année prochaine`
|
|
780
|
-
])
|
|
883
|
+
]),
|
|
884
|
+
...nextWeekdayPhrases
|
|
781
885
|
];
|
|
782
886
|
const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
|
|
783
887
|
const boundaryPrefixes = [
|
|
@@ -826,6 +930,17 @@ const suggestFrench = (input, limit) => {
|
|
|
826
930
|
], limit);
|
|
827
931
|
};
|
|
828
932
|
const renderFrench = (range) => {
|
|
933
|
+
const shifted = decomposeShiftedPeriodRange(range);
|
|
934
|
+
if (Option.isSome(shifted)) {
|
|
935
|
+
const base = renderFrench(shifted.value.baseRange);
|
|
936
|
+
const entry = units.find((unit) => unit.unit === shifted.value.unit);
|
|
937
|
+
if (Option.isSome(base) && entry !== void 0) {
|
|
938
|
+
const amount = Math.abs(shifted.value.amount);
|
|
939
|
+
const noun = amount === 1 ? entry.singular : entry.plural;
|
|
940
|
+
const direction = shifted.value.amount < 0 ? "il y a" : "dans";
|
|
941
|
+
return Option.some(`${base.value} ${direction} ${amount} ${noun}`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
829
944
|
const offset = calendarPeriodOffset(range);
|
|
830
945
|
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
831
946
|
const entry = units.find((unit) => unit.unit === offset.value.unit);
|
|
@@ -852,11 +967,12 @@ const renderFrench = (range) => {
|
|
|
852
967
|
];
|
|
853
968
|
return renderPeriodRange(range, [...units.map((entry) => candidate(periodToDateRange(entry.unit), entry.toDate)), ...units.map((entry) => candidate(remainingPeriodRange(entry.unit), entry.remaining))], periods, (period) => `depuis ${period}`, (period) => `avant ${period}`, untilLabel, (period) => `après ${period}`, rangeLabel, (period) => `depuis ${period} jusqu'à maintenant`, (period) => `de maintenant à ${period}`, () => "jusqu'à maintenant", () => "depuis maintenant");
|
|
854
969
|
};
|
|
855
|
-
const normalizeFrench = (input, locale) => normalizeNaturalText(input, locale).replaceAll("’", "'");
|
|
970
|
+
const normalizeFrench = (input, locale) => normalizeFrenchCounts(normalizeNaturalText(input, locale).replaceAll("’", "'"));
|
|
856
971
|
const FrenchContribution = new BaseLanguageContribution({
|
|
857
972
|
locale: "fr",
|
|
858
973
|
vocabulary: [
|
|
859
974
|
...months,
|
|
975
|
+
...weekdays,
|
|
860
976
|
...monthAbbreviations.flatMap((aliases) => aliases),
|
|
861
977
|
...units.flatMap((entry) => [
|
|
862
978
|
entry.singular,
|
|
@@ -887,7 +1003,7 @@ const FrenchContribution = new BaseLanguageContribution({
|
|
|
887
1003
|
"jusqu'à"
|
|
888
1004
|
],
|
|
889
1005
|
normalize: normalizeFrench,
|
|
890
|
-
correct:
|
|
1006
|
+
correct: correctFrench,
|
|
891
1007
|
parseExact: parseFrench,
|
|
892
1008
|
suggest: suggestFrench,
|
|
893
1009
|
render: renderFrench
|
package/dist/locales/nl.mjs
CHANGED
|
@@ -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,
|
|
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, periodDay, 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/nl.ts
|
|
10
9
|
const months = [
|
|
@@ -21,6 +20,54 @@ const months = [
|
|
|
21
20
|
"november",
|
|
22
21
|
"december"
|
|
23
22
|
];
|
|
23
|
+
const weekdays = [
|
|
24
|
+
"maandag",
|
|
25
|
+
"dinsdag",
|
|
26
|
+
"woensdag",
|
|
27
|
+
"donderdag",
|
|
28
|
+
"vrijdag",
|
|
29
|
+
"zaterdag",
|
|
30
|
+
"zondag"
|
|
31
|
+
];
|
|
32
|
+
const nextWeekdayPhrases = weekdays.map((weekday) => `volgende ${weekday}`);
|
|
33
|
+
const dutchCountWords = [
|
|
34
|
+
"twee",
|
|
35
|
+
"drie",
|
|
36
|
+
"vier",
|
|
37
|
+
"vijf",
|
|
38
|
+
"zes",
|
|
39
|
+
"zeven",
|
|
40
|
+
"acht",
|
|
41
|
+
"negen",
|
|
42
|
+
"tien",
|
|
43
|
+
"elf",
|
|
44
|
+
"twaalf",
|
|
45
|
+
"dertien",
|
|
46
|
+
"veertien",
|
|
47
|
+
"vijftien",
|
|
48
|
+
"zestien",
|
|
49
|
+
"zeventien",
|
|
50
|
+
"achttien",
|
|
51
|
+
"negentien",
|
|
52
|
+
"twintig"
|
|
53
|
+
];
|
|
54
|
+
const dutchCountOnes = [[1, "een"], ...dutchCountWords.slice(0, 8).map((word, index) => [index + 2, word])];
|
|
55
|
+
const dutchCountAliases = [...sequentialCountAliases(dutchCountWords.map((word) => [word]), 2), ...compoundCountAliases(decimalTens([
|
|
56
|
+
"twintig",
|
|
57
|
+
"dertig",
|
|
58
|
+
"veertig",
|
|
59
|
+
"vijftig",
|
|
60
|
+
"zestig",
|
|
61
|
+
"zeventig",
|
|
62
|
+
"tachtig",
|
|
63
|
+
"negentig"
|
|
64
|
+
]), dutchCountOnes, (ten, one) => {
|
|
65
|
+
return [`${one === "twee" || one === "drie" ? `${one}ën` : `${one}en`}${ten}`];
|
|
66
|
+
})];
|
|
67
|
+
const normalizeDutchCounts = compileCountAliasNormalizer(dutchCountAliases);
|
|
68
|
+
const dutchCountVocabulary = new Set(countAliasVocabulary(dutchCountAliases));
|
|
69
|
+
const correctDutch = (input, vocabulary) => correctWhitespaceSeparatedText(input, vocabulary, dutchCountVocabulary);
|
|
70
|
+
const normalizeDutch = (input, locale) => normalizeDutchCounts(normalizeNaturalText(input, locale));
|
|
24
71
|
const monthAbbreviations = [
|
|
25
72
|
["jan"],
|
|
26
73
|
["feb"],
|
|
@@ -350,15 +397,7 @@ const parseNamedDate = (input) => {
|
|
|
350
397
|
const named = String$1.match(/^(?:de )?([0-3]?\d)(?:e|ste|de)?(?: van)? ([a-z]+\.?)(?: van)? (\d{4})$/u)(input);
|
|
351
398
|
if (Option.isSome(named)) return namedDatePeriod(textAt(named.value, 3), textAt(named.value, 2), textAt(named.value, 1), monthNumber, dateLabel);
|
|
352
399
|
const numeric = String$1.match(/^([0-3]?\d)[./-]([01]?\d)[./-](\d{4})$/u)(input);
|
|
353
|
-
if (Option.isSome(numeric))
|
|
354
|
-
const year = validYear(textAt(numeric.value, 3));
|
|
355
|
-
const month = Number(textAt(numeric.value, 2));
|
|
356
|
-
const day = Number(textAt(numeric.value, 1));
|
|
357
|
-
if (year !== void 0 && month >= 1 && month <= 12) {
|
|
358
|
-
const value = isoDate(year, month, day);
|
|
359
|
-
if (isIsoDate(value) && value !== "9999-12-31") return Option.some(fixedDatePeriod(value, dateLabel(day, month, year)));
|
|
360
|
-
}
|
|
361
|
-
}
|
|
400
|
+
if (Option.isSome(numeric)) return namedDatePeriod(textAt(numeric.value, 3), textAt(numeric.value, 2), textAt(numeric.value, 1), Number, dateLabel);
|
|
362
401
|
const current = String$1.match(/^(?:de )?([0-3]?\d)(?:e|ste|de)?(?: van)? ([a-z]+\.?)$/u)(input);
|
|
363
402
|
if (Option.isSome(current)) return namedCurrentYearDatePeriod(textAt(current.value, 2), textAt(current.value, 1), monthNumber, currentDateLabel);
|
|
364
403
|
const relative = String$1.match(/^(?:de )?([0-3]?\d)(?:e|ste|de)? van (.+)$/u)(input);
|
|
@@ -395,13 +434,13 @@ const parseQuarter = (input) => {
|
|
|
395
434
|
const quarter = quarterNumber(textAt(standalone.value, 1));
|
|
396
435
|
return quarter === void 0 ? Option.none() : Option.some(quarterOfRelativeYear(quarter, 0, `K${quarter}`));
|
|
397
436
|
};
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (Option.isSome(
|
|
437
|
+
const parseDatedPeriod = (input) => {
|
|
438
|
+
const knownPeriod = Option.firstSomeOf([
|
|
439
|
+
absoluteDatePeriod(input, "nl"),
|
|
440
|
+
parseNamedDate(input),
|
|
441
|
+
parseQuarter(input)
|
|
442
|
+
]);
|
|
443
|
+
if (Option.isSome(knownPeriod)) return knownPeriod;
|
|
405
444
|
const yearMatch = String$1.match(/^(?:het jaar |jaar )?(\d{4})$/u)(input);
|
|
406
445
|
if (Option.isSome(yearMatch)) {
|
|
407
446
|
const year = validYear(textAt(yearMatch.value, 1));
|
|
@@ -413,6 +452,20 @@ const parseBasePeriod = (input) => {
|
|
|
413
452
|
const year = validYear(textAt(monthYear.value, 2));
|
|
414
453
|
if (month !== void 0 && year !== void 0) return Option.some(fixedMonthPeriod(year, month, `${textAt(months, month - 1)} ${year}`));
|
|
415
454
|
}
|
|
455
|
+
return Option.none();
|
|
456
|
+
};
|
|
457
|
+
const parseBasePeriod = (input) => {
|
|
458
|
+
const datedPeriod = parseDatedPeriod(input);
|
|
459
|
+
if (Option.isSome(datedPeriod)) return datedPeriod;
|
|
460
|
+
const prefixedRelativeMonth = String$1.match(/^(vorige|deze|volgende) ([a-z]+\.?)$/u)(input);
|
|
461
|
+
if (Option.isSome(prefixedRelativeMonth)) {
|
|
462
|
+
const month = monthNumber(textAt(prefixedRelativeMonth.value, 2));
|
|
463
|
+
if (month !== void 0) {
|
|
464
|
+
const modifier = textAt(prefixedRelativeMonth.value, 1);
|
|
465
|
+
const direction = relativeYearDirection(modifier);
|
|
466
|
+
return Option.some(monthOfRelativeYear(month, direction, `${modifier} ${textAt(months, month - 1)}`));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
416
469
|
const relativeMonth = String$1.match(/^([a-z]+\.?)(?: van)? (vorig jaar|volgend jaar|dit jaar)$/u)(input);
|
|
417
470
|
const relativeMonthYearFirst = String$1.match(/^(vorig jaar|volgend jaar|dit jaar) ([a-z]+\.?)$/u)(input);
|
|
418
471
|
const relativeMatch = Option.firstSomeOf([relativeMonth, relativeMonthYearFirst]);
|
|
@@ -445,13 +498,33 @@ const parseBasePeriod = (input) => {
|
|
|
445
498
|
].includes(input)) return Option.some(relativeWeekend(1, "volgend weekend"));
|
|
446
499
|
if (input === "het weekend voor het vorige") return Option.some(relativeWeekend(-2, input));
|
|
447
500
|
if (input === "het weekend na het volgende") return Option.some(relativeWeekend(2, input));
|
|
448
|
-
|
|
501
|
+
const weekday = nextWeekdayPhrases.indexOf(input);
|
|
502
|
+
return weekday === -1 ? Option.none() : Option.some(relativeWeekday(weekday, 1, nextWeekdayPhrases[weekday] ?? input));
|
|
449
503
|
};
|
|
450
504
|
const parsePeriod = (input) => {
|
|
505
|
+
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);
|
|
506
|
+
if (Option.isSome(shifted)) {
|
|
507
|
+
const suffixAmount = textAt(shifted.value, 4);
|
|
508
|
+
const amount = parseTrailingCount(suffixAmount || textAt(shifted.value, 2));
|
|
509
|
+
const unitText = textAt(shifted.value, suffixAmount.length > 0 ? 5 : 3);
|
|
510
|
+
const alias = unitAliases.find((unit) => unit[0] === unitText);
|
|
511
|
+
const entry = alias === void 0 ? void 0 : units.find((unit) => unit.unit === alias[1]);
|
|
512
|
+
const period = parsePeriod(textAt(shifted.value, 1));
|
|
513
|
+
if (Option.isSome(amount) && entry !== void 0 && Option.isSome(period)) {
|
|
514
|
+
const past = textAt(shifted.value, 6) === "geleden";
|
|
515
|
+
const direction = past ? -amount.value : amount.value;
|
|
516
|
+
const noun = amount.value === 1 ? entry.singular : entry.plural;
|
|
517
|
+
const canonical = past ? `${period.value.canonical} ${amount.value} ${noun} geleden` : `${period.value.canonical} over ${amount.value} ${noun}`;
|
|
518
|
+
return Option.some(shiftPeriod(period.value, direction, entry.unit, canonical));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
451
521
|
const edge = String$1.match(/^(?:het )?(begin|eind|einde)(?: van)? (.+)$/u)(input);
|
|
452
522
|
if (Option.isSome(edge)) {
|
|
453
523
|
const edgeName = textAt(edge.value, 1);
|
|
454
|
-
const
|
|
524
|
+
const periodText = textAt(edge.value, 2);
|
|
525
|
+
const basePeriod = parseBasePeriod(periodText);
|
|
526
|
+
const implicit = units.find((entry) => `${entry.article} ${entry.singular}` === periodText);
|
|
527
|
+
const period = Option.isSome(basePeriod) || implicit === void 0 ? basePeriod : Option.some(relativePeriod(implicit.unit, 0, implicit.current));
|
|
455
528
|
if (Option.isSome(period)) {
|
|
456
529
|
const isEnd = edgeName === "eind" || edgeName === "einde";
|
|
457
530
|
const canonical = `${isEnd ? "eind" : "begin"} van ${period.value.canonical}`;
|
|
@@ -465,21 +538,20 @@ const parsePeriod = (input) => {
|
|
|
465
538
|
"de hele ",
|
|
466
539
|
"het hele "
|
|
467
540
|
].find((prefix) => input.startsWith(prefix));
|
|
468
|
-
|
|
541
|
+
const base = parseBasePeriod(wrapper === void 0 ? input : input.slice(wrapper.length));
|
|
542
|
+
return Option.isSome(base) ? base : parseCalendarOffset(input);
|
|
469
543
|
};
|
|
470
544
|
const countedUnit = (value, amount) => {
|
|
471
545
|
const expected = amount === 1 ? "singular" : "plural";
|
|
472
546
|
const alias = unitAliases.find((entry) => entry[0] === value && (entry[2] === expected || entry[2] === "both"));
|
|
473
547
|
return alias === void 0 ? void 0 : units.find((entry) => entry.unit === alias[1]);
|
|
474
548
|
};
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
const
|
|
481
|
-
const rollingSincePattern = countedPattern("^(?:sinds|gedurende) ([1-9]\\d*) (UNIT)$");
|
|
482
|
-
const rollingBarePattern = countedPattern("^([1-9]\\d*) (UNIT)$");
|
|
549
|
+
const calendarPastPattern = /^([1-9]\d*) ([^ ]+) geleden$/u;
|
|
550
|
+
const calendarFuturePatterns = [/^(?:over|binnen) ([1-9]\d*) ([^ ]+)$/u, /^([1-9]\d*) ([^ ]+) later$/u];
|
|
551
|
+
const rollingPastPatterns = [/^(?:(?:de|in de) )?(?:afgelopen|laatste|vorige) ([1-9]\d*) ([^ ]+)$/u, /^([1-9]\d*) (?:afgelopen|laatste|vorige) ([^ ]+)$/u];
|
|
552
|
+
const rollingFuturePatterns = [/^(?:(?:de|in de|binnen de) )?(?:komende|volgende|aankomende) ([1-9]\d*) ([^ ]+)$/u, /^([1-9]\d*) (?:komende|volgende|aankomende) ([^ ]+)$/u];
|
|
553
|
+
const rollingSincePattern = /^(?:sinds|gedurende) ([1-9]\d*) ([^ ]+)$/u;
|
|
554
|
+
const rollingBarePattern = /^([1-9]\d*) ([^ ]+)$/u;
|
|
483
555
|
const firstPatternMatch = (input, patterns) => Option.firstSomeOf(patterns.map((pattern) => String$1.match(pattern)(input)));
|
|
484
556
|
const singularRollingCanonical = (entry, future) => future ? `vanaf nu gedurende een ${entry.singular}` : `sinds een ${entry.singular}`;
|
|
485
557
|
const singularRollingPhrases = units.flatMap((entry) => [
|
|
@@ -528,7 +600,7 @@ const singularCalendarOffsets = units.flatMap((entry) => [
|
|
|
528
600
|
]);
|
|
529
601
|
const parseCalendarOffset = (input) => {
|
|
530
602
|
const singular = singularCalendarOffsets.find((entry) => entry.phrase === input);
|
|
531
|
-
if (singular !== void 0) return Option.some(
|
|
603
|
+
if (singular !== void 0) return Option.some(relativePeriod(singular.entry.unit, singular.direction, singular.phrase));
|
|
532
604
|
const past = String$1.match(calendarPastPattern)(input);
|
|
533
605
|
const future = firstPatternMatch(input, calendarFuturePatterns);
|
|
534
606
|
const match = Option.firstSomeOf([past, future]);
|
|
@@ -540,7 +612,7 @@ const parseCalendarOffset = (input) => {
|
|
|
540
612
|
const direction = Option.isSome(past) ? -amount.value : amount.value;
|
|
541
613
|
const noun = amount.value === 1 ? entry.singular : entry.plural;
|
|
542
614
|
const canonical = direction < 0 ? `${amount.value} ${noun} geleden` : `over ${amount.value} ${noun}`;
|
|
543
|
-
return Option.some(
|
|
615
|
+
return Option.some(relativePeriod(entry.unit, direction, canonical));
|
|
544
616
|
};
|
|
545
617
|
const parseRollingPeriod = (input) => {
|
|
546
618
|
const singular = singularRollingPhrases.find((entry) => entry.phrase === input);
|
|
@@ -604,8 +676,6 @@ const parseDutch = (input) => {
|
|
|
604
676
|
"sinds nu",
|
|
605
677
|
"voortaan"
|
|
606
678
|
].includes(input)) return Option.some(candidate(fromNowRange(), "vanaf nu"));
|
|
607
|
-
const offset = parseCalendarOffset(input);
|
|
608
|
-
if (Option.isSome(offset)) return offset;
|
|
609
679
|
const rolling = parseRollingPeriod(input);
|
|
610
680
|
if (Option.isSome(rolling)) return rolling;
|
|
611
681
|
const toDate = toDatePhrases.find((entry) => entry.phrase === input);
|
|
@@ -619,7 +689,8 @@ const parseDutch = (input) => {
|
|
|
619
689
|
], [
|
|
620
690
|
"vanaf nu tot en met ",
|
|
621
691
|
"van vandaag tot en met ",
|
|
622
|
-
"tussen vandaag en "
|
|
692
|
+
"tussen vandaag en ",
|
|
693
|
+
"nu tot en met "
|
|
623
694
|
], parsePeriod, (period) => `vanaf ${period} tot nu toe`, (period) => `vanaf nu tot en met ${period}`);
|
|
624
695
|
if (Option.isSome(nowBounded)) return nowBounded;
|
|
625
696
|
const bounded = joinedPeriodCandidate(input, [
|
|
@@ -670,9 +741,12 @@ const staticPeriodPhrases = [
|
|
|
670
741
|
]),
|
|
671
742
|
...months.flatMap((month) => [
|
|
672
743
|
month,
|
|
744
|
+
`vorige ${month}`,
|
|
745
|
+
`volgende ${month}`,
|
|
673
746
|
`${month} vorig jaar`,
|
|
674
747
|
`${month} volgend jaar`
|
|
675
|
-
])
|
|
748
|
+
]),
|
|
749
|
+
...nextWeekdayPhrases
|
|
676
750
|
];
|
|
677
751
|
const staticPeriods = periodsFromPhrases(staticPeriodPhrases, parsePeriod);
|
|
678
752
|
const boundaryPrefixes = [
|
|
@@ -719,6 +793,16 @@ const suggestDutch = (input, limit) => {
|
|
|
719
793
|
], limit);
|
|
720
794
|
};
|
|
721
795
|
const renderDutch = (range) => {
|
|
796
|
+
const shifted = decomposeShiftedPeriodRange(range);
|
|
797
|
+
if (Option.isSome(shifted)) {
|
|
798
|
+
const base = renderDutch(shifted.value.baseRange);
|
|
799
|
+
const entry = units.find((unit) => unit.unit === shifted.value.unit);
|
|
800
|
+
if (Option.isSome(base) && entry !== void 0) {
|
|
801
|
+
const amount = Math.abs(shifted.value.amount);
|
|
802
|
+
const noun = amount === 1 ? entry.singular : entry.plural;
|
|
803
|
+
return Option.some(shifted.value.amount < 0 ? `${base.value} ${amount} ${noun} geleden` : `${base.value} over ${amount} ${noun}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
722
806
|
const offset = calendarPeriodOffset(range);
|
|
723
807
|
if (Option.isSome(offset) && Math.abs(offset.value.amount) > 1) {
|
|
724
808
|
const entry = units.find((unit) => unit.unit === offset.value.unit);
|
|
@@ -749,6 +833,7 @@ const DutchContribution = new BaseLanguageContribution({
|
|
|
749
833
|
locale: "nl",
|
|
750
834
|
vocabulary: [
|
|
751
835
|
...months,
|
|
836
|
+
...weekdays,
|
|
752
837
|
...monthAbbreviations.flatMap((aliases) => aliases),
|
|
753
838
|
...units.flatMap((entry) => [
|
|
754
839
|
entry.singular,
|
|
@@ -763,10 +848,13 @@ const DutchContribution = new BaseLanguageContribution({
|
|
|
763
848
|
"begin",
|
|
764
849
|
"binnen",
|
|
765
850
|
"eind",
|
|
851
|
+
"en",
|
|
766
852
|
"geleden",
|
|
767
853
|
"komende",
|
|
768
854
|
"laatste",
|
|
855
|
+
"met",
|
|
769
856
|
"na",
|
|
857
|
+
"nu",
|
|
770
858
|
"over",
|
|
771
859
|
"rest",
|
|
772
860
|
"sinds",
|
|
@@ -776,8 +864,8 @@ const DutchContribution = new BaseLanguageContribution({
|
|
|
776
864
|
"volgend",
|
|
777
865
|
"voor"
|
|
778
866
|
],
|
|
779
|
-
normalize:
|
|
780
|
-
correct:
|
|
867
|
+
normalize: normalizeDutch,
|
|
868
|
+
correct: correctDutch,
|
|
781
869
|
parseExact: parseDutch,
|
|
782
870
|
suggest: suggestDutch,
|
|
783
871
|
render: renderDutch
|