feat: Add natural language date parser with multi-lang support (#13615)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -35,89 +35,93 @@ export const findStartOfNextMonth = currentDate => {
|
||||
});
|
||||
};
|
||||
|
||||
export const findNextDay = currentDate => {
|
||||
return add(currentDate, { days: 1 });
|
||||
};
|
||||
export const findNextDay = currentDate => add(currentDate, { days: 1 });
|
||||
|
||||
export const setHoursToNine = date => {
|
||||
return setSeconds(setMinutes(setHours(date, 9), 0), 0);
|
||||
export const setHoursToNine = date =>
|
||||
setSeconds(setMinutes(setHours(date, 9), 0), 0);
|
||||
|
||||
const SNOOZE_RESOLVERS = {
|
||||
[SNOOZE_OPTIONS.AN_HOUR_FROM_NOW]: d => add(d, { hours: 1 }),
|
||||
[SNOOZE_OPTIONS.UNTIL_TOMORROW]: d => setHoursToNine(findNextDay(d)),
|
||||
[SNOOZE_OPTIONS.UNTIL_NEXT_WEEK]: d => setHoursToNine(findStartOfNextWeek(d)),
|
||||
[SNOOZE_OPTIONS.UNTIL_NEXT_MONTH]: d =>
|
||||
setHoursToNine(findStartOfNextMonth(d)),
|
||||
};
|
||||
|
||||
export const findSnoozeTime = (snoozeType, currentDate = new Date()) => {
|
||||
let parsedDate = null;
|
||||
if (snoozeType === SNOOZE_OPTIONS.AN_HOUR_FROM_NOW) {
|
||||
parsedDate = add(currentDate, { hours: 1 });
|
||||
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_TOMORROW) {
|
||||
parsedDate = setHoursToNine(findNextDay(currentDate));
|
||||
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_WEEK) {
|
||||
parsedDate = setHoursToNine(findStartOfNextWeek(currentDate));
|
||||
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_MONTH) {
|
||||
parsedDate = setHoursToNine(findStartOfNextMonth(currentDate));
|
||||
}
|
||||
|
||||
return parsedDate ? getUnixTime(parsedDate) : null;
|
||||
const resolve = SNOOZE_RESOLVERS[snoozeType];
|
||||
return resolve ? getUnixTime(resolve(currentDate)) : null;
|
||||
};
|
||||
|
||||
export const snoozedReopenTime = snoozedUntil => {
|
||||
if (!snoozedUntil) {
|
||||
return null;
|
||||
}
|
||||
if (!snoozedUntil) return null;
|
||||
const date = new Date(snoozedUntil);
|
||||
|
||||
if (isToday(date)) {
|
||||
return format(date, 'h.mmaaa');
|
||||
}
|
||||
if (!isSameYear(date, new Date())) {
|
||||
return format(date, 'd MMM yyyy, h.mmaaa');
|
||||
}
|
||||
return snoozedUntil ? format(date, 'd MMM, h.mmaaa') : null;
|
||||
if (isToday(date)) return format(date, 'h.mmaaa');
|
||||
if (!isSameYear(date, new Date())) return format(date, 'd MMM yyyy, h.mmaaa');
|
||||
return format(date, 'd MMM, h.mmaaa');
|
||||
};
|
||||
|
||||
export const snoozedReopenTimeToTimestamp = snoozedUntil => {
|
||||
return snoozedUntil ? getUnixTime(new Date(snoozedUntil)) : null;
|
||||
};
|
||||
const formatSnoozeDate = (snoozeDate, currentDate) => {
|
||||
return isSameYear(snoozeDate, currentDate)
|
||||
? format(snoozeDate, 'EEE, d MMM, h:mm a')
|
||||
: format(snoozeDate, 'EEE, d MMM yyyy, h:mm a');
|
||||
export const snoozedReopenTimeToTimestamp = snoozedUntil =>
|
||||
snoozedUntil ? getUnixTime(new Date(snoozedUntil)) : null;
|
||||
|
||||
const formatSnoozeDate = (snoozeDate, currentDate, locale = 'en') => {
|
||||
const sameYear = isSameYear(snoozeDate, currentDate);
|
||||
try {
|
||||
const opts = {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
...(sameYear ? {} : { year: 'numeric' }),
|
||||
};
|
||||
return new Intl.DateTimeFormat(locale, opts).format(snoozeDate);
|
||||
} catch {
|
||||
return sameYear
|
||||
? format(snoozeDate, 'EEE, d MMM, h:mm a')
|
||||
: format(snoozeDate, 'EEE, d MMM yyyy, h:mm a');
|
||||
}
|
||||
};
|
||||
|
||||
const capitalizeLabel = text => text.replace(/^\w/, c => c.toUpperCase());
|
||||
|
||||
export const generateSnoozeSuggestions = (
|
||||
searchText,
|
||||
currentDate = new Date()
|
||||
currentDate = new Date(),
|
||||
{ translations, locale } = {}
|
||||
) => {
|
||||
const suggestions = generateDateSuggestions(searchText, currentDate);
|
||||
const suggestions = generateDateSuggestions(searchText, currentDate, {
|
||||
translations,
|
||||
locale,
|
||||
});
|
||||
return suggestions.map(s => ({
|
||||
date: s.date,
|
||||
unixTime: s.unix,
|
||||
label: capitalizeLabel(s.label),
|
||||
formattedDate: formatSnoozeDate(s.date, currentDate),
|
||||
formattedDate: formatSnoozeDate(s.date, currentDate, locale),
|
||||
}));
|
||||
};
|
||||
|
||||
const UNIT_SHORT = {
|
||||
minute: 'm',
|
||||
minutes: 'm',
|
||||
hour: 'h',
|
||||
hours: 'h',
|
||||
day: 'd',
|
||||
days: 'd',
|
||||
month: 'mo',
|
||||
months: 'mo',
|
||||
year: 'y',
|
||||
years: 'y',
|
||||
};
|
||||
|
||||
export const shortenSnoozeTime = snoozedUntil => {
|
||||
if (!snoozedUntil) {
|
||||
return null;
|
||||
}
|
||||
const unitMap = {
|
||||
minutes: 'm',
|
||||
minute: 'm',
|
||||
hours: 'h',
|
||||
hour: 'h',
|
||||
days: 'd',
|
||||
day: 'd',
|
||||
months: 'mo',
|
||||
month: 'mo',
|
||||
years: 'y',
|
||||
year: 'y',
|
||||
};
|
||||
const shortenTime = snoozedUntil
|
||||
if (!snoozedUntil) return null;
|
||||
return snoozedUntil
|
||||
.replace(/^in\s+/i, '')
|
||||
.replace(
|
||||
/\s(minute|hour|day|month|year)s?\b/gi,
|
||||
(match, unit) => unitMap[unit.toLowerCase()] || match
|
||||
(match, unit) => UNIT_SHORT[unit.toLowerCase()] || match
|
||||
);
|
||||
|
||||
return shortenTime;
|
||||
};
|
||||
|
||||
@@ -816,6 +816,378 @@ describe('generateDateSuggestions', () => {
|
||||
expect(results.length).toBeLessThanOrEqual(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('smart compositional suggestions', () => {
|
||||
it('"mon" suggests monday + time-of-day variants (noon, afternoon, evening, night)', () => {
|
||||
const results = generateDateSuggestions('mon', now);
|
||||
const labels = results.map(r => r.label);
|
||||
// "monday morning" (9am) is deduped with "monday" (default 9am), so noon+ appear
|
||||
expect(labels.some(l => /monday\s+afternoon/.test(l))).toBe(true);
|
||||
expect(labels.some(l => /monday\s+evening/.test(l))).toBe(true);
|
||||
});
|
||||
|
||||
it('"monday" suggests multiple time-of-day variants', () => {
|
||||
const results = generateDateSuggestions('monday', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => l.includes('monday afternoon'))).toBe(true);
|
||||
expect(labels.some(l => l.includes('monday evening'))).toBe(true);
|
||||
expect(results.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it('"fri" suggests friday + time-of-day variants', () => {
|
||||
const results = generateDateSuggestions('fri', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => /friday/.test(l))).toBe(true);
|
||||
expect(results.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it('"tomorrow m" suggests tomorrow morning', () => {
|
||||
const results = generateDateSuggestions('tomorrow m', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => l.includes('tomorrow morning'))).toBe(true);
|
||||
});
|
||||
|
||||
it('"tomorrow a" suggests tomorrow afternoon', () => {
|
||||
const results = generateDateSuggestions('tomorrow a', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => l.includes('tomorrow afternoon'))).toBe(true);
|
||||
});
|
||||
|
||||
it('"next mon" suggests next monday and next month', () => {
|
||||
const results = generateDateSuggestions('next mon', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => l.includes('next mon'))).toBe(true);
|
||||
});
|
||||
|
||||
it('"next monday m" suggests next monday morning', () => {
|
||||
const results = generateDateSuggestions('next monday m', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => l.includes('next monday morning'))).toBe(true);
|
||||
});
|
||||
|
||||
it('"t" suggests today, tonight, tomorrow', () => {
|
||||
const results = generateDateSuggestions('t', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(
|
||||
labels.some(l => l === 'today' || l === 'tonight' || l === 'tomorrow')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('"n" suggests next week, next month, next weekdays', () => {
|
||||
const results = generateDateSuggestions('n', now);
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => l.includes('next'))).toBe(true);
|
||||
});
|
||||
|
||||
it('all suggestions parse to valid future dates', () => {
|
||||
const inputs = ['mon', 'monday', 'fri', 'tomorrow m', 'next mon', 't'];
|
||||
inputs.forEach(input => {
|
||||
const results = generateDateSuggestions(input, now);
|
||||
results.forEach(r => {
|
||||
expect(r.date).toBeInstanceOf(Date);
|
||||
expect(r.date > now).toBe(true);
|
||||
expect(typeof r.unix).toBe('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bare number + time-of-day context inference', () => {
|
||||
it('"morning 6" parses to 6am', () => {
|
||||
const result = parseDateFromText('morning 6', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(6);
|
||||
});
|
||||
|
||||
it('"evening 7" parses to 7pm (19:00)', () => {
|
||||
const result = parseDateFromText('evening 7', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(19);
|
||||
});
|
||||
|
||||
it('"afternoon 3" parses to 3pm (15:00)', () => {
|
||||
const result = parseDateFromText('afternoon 3', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(15);
|
||||
});
|
||||
|
||||
it('"night 9" parses to 9pm (21:00)', () => {
|
||||
const result = parseDateFromText('night 9', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(21);
|
||||
});
|
||||
|
||||
it('"tomorrow morning 6" parses to tomorrow 6am', () => {
|
||||
const result = parseDateFromText('tomorrow morning 6', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getDate()).toBe(17);
|
||||
expect(result.date.getHours()).toBe(6);
|
||||
});
|
||||
|
||||
it('"tomorrow evening 7" parses to tomorrow 7pm', () => {
|
||||
const result = parseDateFromText('tomorrow evening 7', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getDate()).toBe(17);
|
||||
expect(result.date.getHours()).toBe(19);
|
||||
});
|
||||
|
||||
it('"monday morning 6" parses to next monday 6am', () => {
|
||||
const result = parseDateFromText('monday morning 6', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(6);
|
||||
});
|
||||
|
||||
it('"friday evening 8" parses to friday 8pm', () => {
|
||||
const result = parseDateFromText('friday evening 8', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(20);
|
||||
});
|
||||
|
||||
it('explicit meridiem still works: "morning 6am" → 6am', () => {
|
||||
const result = parseDateFromText('morning 6am', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(6);
|
||||
});
|
||||
|
||||
it('contradictory meridiem still rejected: "morning 7pm" → null', () => {
|
||||
expect(parseDateFromText('morning 7pm', now)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('localized suggestions with Malayalam translations', () => {
|
||||
const mlTranslations = {
|
||||
UNITS: {
|
||||
MINUTE: 'മിനിറ്റ്',
|
||||
MINUTES: 'മിനിറ്റ്',
|
||||
HOUR: 'മണിക്കൂർ',
|
||||
HOURS: 'മണിക്കൂർ',
|
||||
DAY: 'ദിവസം',
|
||||
DAYS: 'ദിവസം',
|
||||
WEEK: 'ആഴ്ച',
|
||||
WEEKS: 'ആഴ്ച',
|
||||
MONTH: 'മാസം',
|
||||
MONTHS: 'മാസം',
|
||||
YEAR: 'വർഷം',
|
||||
YEARS: 'വർഷം',
|
||||
},
|
||||
HALF: 'അര',
|
||||
NEXT: 'അടുത്ത',
|
||||
THIS: 'ഈ',
|
||||
AT: 'സമയം',
|
||||
IN: 'കഴിഞ്ഞ്',
|
||||
FROM_NOW: 'ഇപ്പോൾ മുതൽ',
|
||||
NEXT_YEAR: 'അടുത്ത വർഷം',
|
||||
MERIDIEM: { AM: 'രാവിലെ', PM: 'വൈകുന്നേരം' },
|
||||
RELATIVE: {
|
||||
TOMORROW: 'നാളെ',
|
||||
DAY_AFTER_TOMORROW: 'മറ്റന്നാൾ',
|
||||
NEXT_WEEK: 'അടുത്ത ആഴ്ച',
|
||||
NEXT_MONTH: 'അടുത്ത മാസം',
|
||||
THIS_WEEKEND: 'ഈ വാരാന്ത്യം',
|
||||
NEXT_WEEKEND: 'അടുത്ത വാരാന്ത്യം',
|
||||
},
|
||||
TIME_OF_DAY: {
|
||||
MORNING: 'രാവിലെ',
|
||||
AFTERNOON: 'ഉച്ചയ്ക്ക്',
|
||||
EVENING: 'വൈകുന്നേരം',
|
||||
NIGHT: 'രാത്രി',
|
||||
NOON: 'ഉച്ച',
|
||||
MIDNIGHT: 'അർദ്ധരാത്രി',
|
||||
},
|
||||
WORD_NUMBERS: {
|
||||
ONE: 'ഒന്ന്',
|
||||
TWO: 'രണ്ട്',
|
||||
THREE: 'മൂന്ന്',
|
||||
FOUR: 'നാല്',
|
||||
FIVE: 'അഞ്ച്',
|
||||
SIX: 'ആറ്',
|
||||
SEVEN: 'ഏഴ്',
|
||||
EIGHT: 'എട്ട്',
|
||||
NINE: 'ഒൻപത്',
|
||||
TEN: 'പത്ത്',
|
||||
TWELVE: 'പന്ത്രണ്ട്',
|
||||
FIFTEEN: 'പതിനഞ്ച്',
|
||||
TWENTY: 'ഇരുപത്',
|
||||
THIRTY: 'മുപ്പത്',
|
||||
},
|
||||
};
|
||||
|
||||
it('Malayalam "നാളെ രാവിലെ 6" parses to tomorrow 6am', () => {
|
||||
const results = generateDateSuggestions('നാളെ രാവിലെ 6', now, {
|
||||
translations: mlTranslations,
|
||||
locale: 'ml',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
expect(results[0].date.getHours()).toBe(6);
|
||||
});
|
||||
|
||||
it('Malayalam "നാളെ" (tomorrow) generates multiple suggestions', () => {
|
||||
const results = generateDateSuggestions('നാളെ', now, {
|
||||
translations: mlTranslations,
|
||||
locale: 'ml',
|
||||
});
|
||||
expect(results.length).toBeGreaterThanOrEqual(3);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
});
|
||||
|
||||
it('Malayalam suggestion labels are in Malayalam, not English', () => {
|
||||
const results = generateDateSuggestions('നാളെ', now, {
|
||||
translations: mlTranslations,
|
||||
locale: 'ml',
|
||||
});
|
||||
const labels = results.map(r => r.label);
|
||||
expect(labels.some(l => /നാളെ/.test(l))).toBe(true);
|
||||
expect(labels.every(l => !/\btomorrow\b/.test(l))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chrono-level patterns', () => {
|
||||
describe('tomorrow at TOD', () => {
|
||||
it('"tomorrow at noon" parses to tomorrow 12pm', () => {
|
||||
const result = parseDateFromText('tomorrow at noon', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getDate()).toBe(17);
|
||||
expect(result.date.getHours()).toBe(12);
|
||||
});
|
||||
|
||||
it('"tomorrow at midnight" parses to tomorrow 0am', () => {
|
||||
const result = parseDateFromText('tomorrow at midnight', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(0);
|
||||
});
|
||||
|
||||
it('"tomorrow at evening" parses to tomorrow 6pm', () => {
|
||||
const result = parseDateFromText('tomorrow at evening', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(18);
|
||||
});
|
||||
});
|
||||
|
||||
describe('duration at time', () => {
|
||||
it('"in 2 days at 3pm" parses correctly', () => {
|
||||
const result = parseDateFromText('in 2 days at 3pm', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getDate()).toBe(18);
|
||||
expect(result.date.getHours()).toBe(15);
|
||||
});
|
||||
|
||||
it('"in 1 week at 9am" parses correctly', () => {
|
||||
const result = parseDateFromText('in 1 week at 9am', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(9);
|
||||
});
|
||||
});
|
||||
|
||||
describe('end of period', () => {
|
||||
it('"end of day" parses to today 5pm', () => {
|
||||
const result = parseDateFromText('end of day', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(17);
|
||||
});
|
||||
|
||||
it('"end of the week" parses to next friday 5pm', () => {
|
||||
const result = parseDateFromText('end of the week', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getDay()).toBe(5);
|
||||
expect(result.date.getHours()).toBe(17);
|
||||
});
|
||||
|
||||
it('"end of month" parses to last day of month 5pm', () => {
|
||||
const result = parseDateFromText('end of month', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getDate()).toBe(30);
|
||||
expect(result.date.getHours()).toBe(17);
|
||||
});
|
||||
});
|
||||
|
||||
describe('later today', () => {
|
||||
it('"later today" parses to +3 hours from now', () => {
|
||||
const result = parseDateFromText('later today', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(13);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compound durations', () => {
|
||||
it('"1 hour 30 minutes" parses correctly', () => {
|
||||
const result = parseDateFromText('1 hour 30 minutes', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(11);
|
||||
expect(result.date.getMinutes()).toBe(30);
|
||||
});
|
||||
|
||||
it('"1h30m" parses correctly', () => {
|
||||
const result = parseDateFromText('1h30m', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(11);
|
||||
expect(result.date.getMinutes()).toBe(30);
|
||||
});
|
||||
|
||||
it('"2 hours and 30 minutes" parses correctly', () => {
|
||||
const result = parseDateFromText('2 hours and 30 minutes', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(12);
|
||||
expect(result.date.getMinutes()).toBe(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aliases and shortcuts', () => {
|
||||
it('"tonite" parses to tonight (8pm)', () => {
|
||||
const result = parseDateFromText('tonite', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(20);
|
||||
});
|
||||
|
||||
it('"couple hours" parses to +2 hours', () => {
|
||||
const result = parseDateFromText('couple hours', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(12);
|
||||
});
|
||||
|
||||
it('"couple of hours" parses to +2 hours', () => {
|
||||
const result = parseDateFromText('couple of hours', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(12);
|
||||
});
|
||||
|
||||
it('"few hours" parses to +3 hours', () => {
|
||||
const result = parseDateFromText('few hours', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(13);
|
||||
});
|
||||
|
||||
it('"nxt week" parses like "next week"', () => {
|
||||
const result = parseDateFromText('nxt week', now);
|
||||
expect(result).not.toBeNull();
|
||||
});
|
||||
|
||||
it('"nxt monday" parses like "next monday"', () => {
|
||||
const result = parseDateFromText('nxt monday', now);
|
||||
expect(result).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('weekday bare hour defaults to PM', () => {
|
||||
it('"monday at 3" parses to 3pm', () => {
|
||||
const result = parseDateFromText('monday at 3', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(15);
|
||||
});
|
||||
|
||||
it('"friday at 5" parses to 5pm', () => {
|
||||
const result = parseDateFromText('friday at 5', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(17);
|
||||
});
|
||||
|
||||
it('"monday at 9" stays 9am (hour >= 8)', () => {
|
||||
const result = parseDateFromText('monday at 9', now);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.date.getHours()).toBe(9);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dot-delimited dates', () => {
|
||||
@@ -918,3 +1290,217 @@ describe('decimal duration parsing (only .5 allowed)', () => {
|
||||
expect(parseDateFromText('2.7 days', now)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Multilingual / Localized Input Regressions ─────────────────────────────
|
||||
|
||||
describe('generateDateSuggestions — localized input regressions', () => {
|
||||
const arTranslations = {
|
||||
UNITS: {
|
||||
MINUTE: 'دقيقة',
|
||||
MINUTES: 'دقائق',
|
||||
HOUR: 'ساعة',
|
||||
HOURS: 'ساعات',
|
||||
DAY: 'يوم',
|
||||
DAYS: 'أيام',
|
||||
WEEK: 'أسبوع',
|
||||
WEEKS: 'أسابيع',
|
||||
MONTH: 'شهر',
|
||||
MONTHS: 'أشهر',
|
||||
YEAR: 'سنة',
|
||||
YEARS: 'سنوات',
|
||||
},
|
||||
HALF: 'نصف',
|
||||
NEXT: 'القادم',
|
||||
THIS: 'هذا',
|
||||
AT: 'الساعة',
|
||||
IN: 'في',
|
||||
FROM_NOW: 'من الآن',
|
||||
NEXT_YEAR: 'العام المقبل',
|
||||
MERIDIEM: { AM: 'صباحاً', PM: 'مساءً' },
|
||||
RELATIVE: {
|
||||
TOMORROW: 'غداً',
|
||||
DAY_AFTER_TOMORROW: 'بعد غد',
|
||||
NEXT_WEEK: 'الأسبوع القادم',
|
||||
NEXT_MONTH: 'الشهر القادم',
|
||||
THIS_WEEKEND: 'نهاية هذا الأسبوع',
|
||||
NEXT_WEEKEND: 'نهاية الأسبوع القادم',
|
||||
},
|
||||
TIME_OF_DAY: {
|
||||
MORNING: 'صباحاً',
|
||||
AFTERNOON: 'بعد الظهر',
|
||||
EVENING: 'مساءً',
|
||||
NIGHT: 'ليلاً',
|
||||
NOON: 'ظهراً',
|
||||
MIDNIGHT: 'منتصف الليل',
|
||||
},
|
||||
WORD_NUMBERS: {
|
||||
ONE: 'واحد',
|
||||
TWO: 'اثنان',
|
||||
THREE: 'ثلاثة',
|
||||
FOUR: 'أربعة',
|
||||
FIVE: 'خمسة',
|
||||
SIX: 'ستة',
|
||||
SEVEN: 'سبعة',
|
||||
EIGHT: 'ثمانية',
|
||||
NINE: 'تسعة',
|
||||
TEN: 'عشرة',
|
||||
TWELVE: 'اثنا عشر',
|
||||
FIFTEEN: 'خمسة عشر',
|
||||
TWENTY: 'عشرون',
|
||||
THIRTY: 'ثلاثون',
|
||||
},
|
||||
};
|
||||
|
||||
const hiTranslations = {
|
||||
UNITS: {
|
||||
MINUTE: 'मिनट',
|
||||
MINUTES: 'मिनट',
|
||||
HOUR: 'घंटा',
|
||||
HOURS: 'घंटे',
|
||||
DAY: 'दिन',
|
||||
DAYS: 'दिन',
|
||||
WEEK: 'सप्ताह',
|
||||
WEEKS: 'सप्ताह',
|
||||
MONTH: 'महीना',
|
||||
MONTHS: 'महीने',
|
||||
YEAR: 'साल',
|
||||
YEARS: 'साल',
|
||||
},
|
||||
HALF: 'आधा',
|
||||
NEXT: 'अगला',
|
||||
THIS: 'यह',
|
||||
AT: 'बजे',
|
||||
IN: 'में',
|
||||
FROM_NOW: 'अब से',
|
||||
NEXT_YEAR: 'अगले साल',
|
||||
MERIDIEM: { AM: 'सुबह', PM: 'शाम' },
|
||||
RELATIVE: {
|
||||
TOMORROW: 'कल',
|
||||
DAY_AFTER_TOMORROW: 'परसों',
|
||||
NEXT_WEEK: 'अगले सप्ताह',
|
||||
NEXT_MONTH: 'अगले महीने',
|
||||
THIS_WEEKEND: 'इस सप्ताहांत',
|
||||
NEXT_WEEKEND: 'अगले सप्ताहांत',
|
||||
},
|
||||
TIME_OF_DAY: {
|
||||
MORNING: 'सुबह',
|
||||
AFTERNOON: 'दोपहर',
|
||||
EVENING: 'शाम',
|
||||
NIGHT: 'रात',
|
||||
NOON: 'दोपहर',
|
||||
MIDNIGHT: 'आधी रात',
|
||||
},
|
||||
WORD_NUMBERS: {
|
||||
ONE: 'एक',
|
||||
TWO: 'दो',
|
||||
THREE: 'तीन',
|
||||
FOUR: 'चार',
|
||||
FIVE: 'पाँच',
|
||||
SIX: 'छह',
|
||||
SEVEN: 'सात',
|
||||
EIGHT: 'आठ',
|
||||
NINE: 'नौ',
|
||||
TEN: 'दस',
|
||||
TWELVE: 'बारह',
|
||||
FIFTEEN: 'पंद्रह',
|
||||
TWENTY: 'बीस',
|
||||
THIRTY: 'तीस',
|
||||
},
|
||||
};
|
||||
|
||||
describe('P1: short non-English tokens must NOT produce spurious half-duration suggestions', () => {
|
||||
it('Arabic "غد" does not produce half-duration suggestions', () => {
|
||||
const results = generateDateSuggestions('غد', now, {
|
||||
translations: arTranslations,
|
||||
locale: 'ar',
|
||||
});
|
||||
const halfLabels = results.filter(r => /half/i.test(r.label));
|
||||
expect(halfLabels).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('Hindi "सु" does not produce half-duration suggestions', () => {
|
||||
const results = generateDateSuggestions('सु', now, {
|
||||
translations: hiTranslations,
|
||||
locale: 'hi',
|
||||
});
|
||||
const halfLabels = results.filter(r => /half/i.test(r.label));
|
||||
expect(halfLabels).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('P1: MERIDIEM vs TIME_OF_DAY — "tomorrow morning" must parse in locales where AM = morning', () => {
|
||||
it('Arabic "غداً صباحاً" (tomorrow morning) parses correctly', () => {
|
||||
const results = generateDateSuggestions('غداً صباحاً', now, {
|
||||
translations: arTranslations,
|
||||
locale: 'ar',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
const first = results[0];
|
||||
expect(first.date.getDate()).toBe(17);
|
||||
expect(first.date.getHours()).toBe(9);
|
||||
});
|
||||
|
||||
it('Hindi "कल सुबह" (tomorrow morning) parses correctly', () => {
|
||||
const results = generateDateSuggestions('कल सुबह', now, {
|
||||
translations: hiTranslations,
|
||||
locale: 'hi',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
const first = results[0];
|
||||
expect(first.date.getDate()).toBe(17);
|
||||
expect(first.date.getHours()).toBe(9);
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic localized parsing still works', () => {
|
||||
it('Arabic "غداً" (tomorrow) parses to tomorrow 9am', () => {
|
||||
const results = generateDateSuggestions('غداً', now, {
|
||||
translations: arTranslations,
|
||||
locale: 'ar',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
});
|
||||
|
||||
it('Hindi "कल" (tomorrow) parses to tomorrow', () => {
|
||||
const results = generateDateSuggestions('कल', now, {
|
||||
translations: hiTranslations,
|
||||
locale: 'hi',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
});
|
||||
|
||||
it('Arabic "غداً،" (tomorrow with attached Arabic comma) parses correctly', () => {
|
||||
const results = generateDateSuggestions('غداً،', now, {
|
||||
translations: arTranslations,
|
||||
locale: 'ar',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
});
|
||||
});
|
||||
|
||||
describe('localized Unicode digits', () => {
|
||||
it('Arabic-Indic digits parse in time expressions', () => {
|
||||
const results = generateDateSuggestions('غداً الساعة ١٢:٣٠', now, {
|
||||
translations: arTranslations,
|
||||
locale: 'ar',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
expect(results[0].date.getHours()).toBe(12);
|
||||
expect(results[0].date.getMinutes()).toBe(30);
|
||||
});
|
||||
|
||||
it('Devanagari digits parse in time-of-day expressions', () => {
|
||||
const results = generateDateSuggestions('कल सुबह ६', now, {
|
||||
translations: hiTranslations,
|
||||
locale: 'hi',
|
||||
});
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].date.getDate()).toBe(17);
|
||||
expect(results[0].date.getHours()).toBe(6);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ import setNewPassword from './setNewPassword.json';
|
||||
import settings from './settings.json';
|
||||
import signup from './signup.json';
|
||||
import sla from './sla.json';
|
||||
import snooze from './snooze.json';
|
||||
import teamsSettings from './teamsSettings.json';
|
||||
import whatsappTemplates from './whatsappTemplates.json';
|
||||
|
||||
@@ -72,6 +73,7 @@ export default {
|
||||
...settings,
|
||||
...signup,
|
||||
...sla,
|
||||
...snooze,
|
||||
...teamsSettings,
|
||||
...whatsappTemplates,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"SNOOZE_PARSER": {
|
||||
"UNITS": {
|
||||
"MINUTE": "دقيقة",
|
||||
"MINUTES": "دقائق",
|
||||
"HOUR": "ساعة",
|
||||
"HOURS": "ساعات",
|
||||
"DAY": "يوم",
|
||||
"DAYS": "أيام",
|
||||
"WEEK": "أسبوع",
|
||||
"WEEKS": "أسابيع",
|
||||
"MONTH": "شهر",
|
||||
"MONTHS": "أشهر",
|
||||
"YEAR": "سنة",
|
||||
"YEARS": "سنوات"
|
||||
},
|
||||
"HALF": "نصف",
|
||||
"NEXT": "القادم",
|
||||
"THIS": "هذا",
|
||||
"AT": "الساعة",
|
||||
"IN": "في",
|
||||
"FROM_NOW": "من الآن",
|
||||
"NEXT_YEAR": "العام المقبل",
|
||||
"MERIDIEM": {
|
||||
"AM": "صباحاً",
|
||||
"PM": "مساءً"
|
||||
},
|
||||
"RELATIVE": {
|
||||
"TOMORROW": "غداً",
|
||||
"DAY_AFTER_TOMORROW": "بعد غد",
|
||||
"NEXT_WEEK": "الأسبوع القادم",
|
||||
"NEXT_MONTH": "الشهر القادم",
|
||||
"THIS_WEEKEND": "نهاية هذا الأسبوع",
|
||||
"NEXT_WEEKEND": "نهاية الأسبوع القادم"
|
||||
},
|
||||
"TIME_OF_DAY": {
|
||||
"MORNING": "صباحاً",
|
||||
"AFTERNOON": "بعد الظهر",
|
||||
"EVENING": "مساءً",
|
||||
"NIGHT": "ليلاً",
|
||||
"NOON": "ظهراً",
|
||||
"MIDNIGHT": "منتصف الليل"
|
||||
},
|
||||
"WORD_NUMBERS": {
|
||||
"ONE": "واحد",
|
||||
"TWO": "اثنان",
|
||||
"THREE": "ثلاثة",
|
||||
"FOUR": "أربعة",
|
||||
"FIVE": "خمسة",
|
||||
"SIX": "ستة",
|
||||
"SEVEN": "سبعة",
|
||||
"EIGHT": "ثمانية",
|
||||
"NINE": "تسعة",
|
||||
"TEN": "عشرة",
|
||||
"TWELVE": "اثنا عشر",
|
||||
"FIFTEEN": "خمسة عشر",
|
||||
"TWENTY": "عشرون",
|
||||
"THIRTY": "ثلاثون"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import setNewPassword from './setNewPassword.json';
|
||||
import settings from './settings.json';
|
||||
import signup from './signup.json';
|
||||
import sla from './sla.json';
|
||||
import snooze from './snooze.json';
|
||||
import teamsSettings from './teamsSettings.json';
|
||||
import whatsappTemplates from './whatsappTemplates.json';
|
||||
import contentTemplates from './contentTemplates.json';
|
||||
@@ -77,6 +78,7 @@ export default {
|
||||
...settings,
|
||||
...signup,
|
||||
...sla,
|
||||
...snooze,
|
||||
...teamsSettings,
|
||||
...whatsappTemplates,
|
||||
...contentTemplates,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"SNOOZE_PARSER": {
|
||||
"UNITS": {
|
||||
"MINUTE": "minute",
|
||||
"MINUTES": "minutes",
|
||||
"HOUR": "hour",
|
||||
"HOURS": "hours",
|
||||
"DAY": "day",
|
||||
"DAYS": "days",
|
||||
"WEEK": "week",
|
||||
"WEEKS": "weeks",
|
||||
"MONTH": "month",
|
||||
"MONTHS": "months",
|
||||
"YEAR": "year",
|
||||
"YEARS": "years"
|
||||
},
|
||||
"HALF": "half",
|
||||
"NEXT": "next",
|
||||
"THIS": "this",
|
||||
"AT": "at",
|
||||
"IN": "in",
|
||||
"FROM_NOW": "from now",
|
||||
"NEXT_YEAR": "next year",
|
||||
"MERIDIEM": {
|
||||
"AM": "am",
|
||||
"PM": "pm"
|
||||
},
|
||||
"RELATIVE": {
|
||||
"TOMORROW": "tomorrow",
|
||||
"DAY_AFTER_TOMORROW": "day after tomorrow",
|
||||
"NEXT_WEEK": "next week",
|
||||
"NEXT_MONTH": "next month",
|
||||
"THIS_WEEKEND": "this weekend",
|
||||
"NEXT_WEEKEND": "next weekend"
|
||||
},
|
||||
"TIME_OF_DAY": {
|
||||
"MORNING": "morning",
|
||||
"AFTERNOON": "afternoon",
|
||||
"EVENING": "evening",
|
||||
"NIGHT": "night",
|
||||
"NOON": "noon",
|
||||
"MIDNIGHT": "midnight"
|
||||
},
|
||||
"WORD_NUMBERS": {
|
||||
"ONE": "one",
|
||||
"TWO": "two",
|
||||
"THREE": "three",
|
||||
"FOUR": "four",
|
||||
"FIVE": "five",
|
||||
"SIX": "six",
|
||||
"SEVEN": "seven",
|
||||
"EIGHT": "eight",
|
||||
"NINE": "nine",
|
||||
"TEN": "ten",
|
||||
"TWELVE": "twelve",
|
||||
"FIFTEEN": "fifteen",
|
||||
"TWENTY": "twenty",
|
||||
"THIRTY": "thirty"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import setNewPassword from './setNewPassword.json';
|
||||
import settings from './settings.json';
|
||||
import signup from './signup.json';
|
||||
import sla from './sla.json';
|
||||
import snooze from './snooze.json';
|
||||
import teamsSettings from './teamsSettings.json';
|
||||
import whatsappTemplates from './whatsappTemplates.json';
|
||||
|
||||
@@ -72,6 +73,7 @@ export default {
|
||||
...settings,
|
||||
...signup,
|
||||
...sla,
|
||||
...snooze,
|
||||
...teamsSettings,
|
||||
...whatsappTemplates,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"SNOOZE_PARSER": {
|
||||
"UNITS": {
|
||||
"MINUTE": "minuto",
|
||||
"MINUTES": "minutos",
|
||||
"HOUR": "hora",
|
||||
"HOURS": "horas",
|
||||
"DAY": "día",
|
||||
"DAYS": "días",
|
||||
"WEEK": "semana",
|
||||
"WEEKS": "semanas",
|
||||
"MONTH": "mes",
|
||||
"MONTHS": "meses",
|
||||
"YEAR": "año",
|
||||
"YEARS": "años"
|
||||
},
|
||||
"HALF": "media",
|
||||
"NEXT": "próximo",
|
||||
"THIS": "este",
|
||||
"AT": "a las",
|
||||
"IN": "en",
|
||||
"FROM_NOW": "a partir de ahora",
|
||||
"NEXT_YEAR": "el próximo año",
|
||||
"MERIDIEM": {
|
||||
"AM": "am",
|
||||
"PM": "pm"
|
||||
},
|
||||
"RELATIVE": {
|
||||
"TOMORROW": "mañana",
|
||||
"DAY_AFTER_TOMORROW": "pasado mañana",
|
||||
"NEXT_WEEK": "la próxima semana",
|
||||
"NEXT_MONTH": "el próximo mes",
|
||||
"THIS_WEEKEND": "este fin de semana",
|
||||
"NEXT_WEEKEND": "el próximo fin de semana"
|
||||
},
|
||||
"TIME_OF_DAY": {
|
||||
"MORNING": "mañana",
|
||||
"AFTERNOON": "tarde",
|
||||
"EVENING": "noche",
|
||||
"NIGHT": "noche",
|
||||
"NOON": "mediodía",
|
||||
"MIDNIGHT": "medianoche"
|
||||
},
|
||||
"WORD_NUMBERS": {
|
||||
"ONE": "uno",
|
||||
"TWO": "dos",
|
||||
"THREE": "tres",
|
||||
"FOUR": "cuatro",
|
||||
"FIVE": "cinco",
|
||||
"SIX": "seis",
|
||||
"SEVEN": "siete",
|
||||
"EIGHT": "ocho",
|
||||
"NINE": "nueve",
|
||||
"TEN": "diez",
|
||||
"TWELVE": "doce",
|
||||
"FIFTEEN": "quince",
|
||||
"TWENTY": "veinte",
|
||||
"THIRTY": "treinta"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import setNewPassword from './setNewPassword.json';
|
||||
import settings from './settings.json';
|
||||
import signup from './signup.json';
|
||||
import sla from './sla.json';
|
||||
import snooze from './snooze.json';
|
||||
import teamsSettings from './teamsSettings.json';
|
||||
import whatsappTemplates from './whatsappTemplates.json';
|
||||
|
||||
@@ -72,6 +73,7 @@ export default {
|
||||
...settings,
|
||||
...signup,
|
||||
...sla,
|
||||
...snooze,
|
||||
...teamsSettings,
|
||||
...whatsappTemplates,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"SNOOZE_PARSER": {
|
||||
"UNITS": {
|
||||
"MINUTE": "മിനിറ്റ്",
|
||||
"MINUTES": "മിനിറ്റ്",
|
||||
"HOUR": "മണിക്കൂർ",
|
||||
"HOURS": "മണിക്കൂർ",
|
||||
"DAY": "ദിവസം",
|
||||
"DAYS": "ദിവസം",
|
||||
"WEEK": "ആഴ്ച",
|
||||
"WEEKS": "ആഴ്ച",
|
||||
"MONTH": "മാസം",
|
||||
"MONTHS": "മാസം",
|
||||
"YEAR": "വർഷം",
|
||||
"YEARS": "വർഷം"
|
||||
},
|
||||
"HALF": "അര",
|
||||
"NEXT": "അടുത്ത",
|
||||
"THIS": "ഈ",
|
||||
"AT": "സമയം",
|
||||
"IN": "കഴിഞ്ഞ്",
|
||||
"FROM_NOW": "ഇപ്പോൾ മുതൽ",
|
||||
"NEXT_YEAR": "അടുത്ത വർഷം",
|
||||
"MERIDIEM": { "AM": "രാവിലെ", "PM": "വൈകുന്നേരം" },
|
||||
"RELATIVE": {
|
||||
"TOMORROW": "നാളെ",
|
||||
"DAY_AFTER_TOMORROW": "മറ്റന്നാൾ",
|
||||
"NEXT_WEEK": "അടുത്ത ആഴ്ച",
|
||||
"NEXT_MONTH": "അടുത്ത മാസം",
|
||||
"THIS_WEEKEND": "ഈ വാരാന്ത്യം",
|
||||
"NEXT_WEEKEND": "അടുത്ത വാരാന്ത്യം"
|
||||
},
|
||||
"TIME_OF_DAY": {
|
||||
"MORNING": "രാവിലെ",
|
||||
"AFTERNOON": "ഉച്ചയ്ക്ക്",
|
||||
"EVENING": "വൈകുന്നേരം",
|
||||
"NIGHT": "രാത്രി",
|
||||
"NOON": "ഉച്ച",
|
||||
"MIDNIGHT": "അർദ്ധരാത്രി"
|
||||
},
|
||||
"WORD_NUMBERS": {
|
||||
"ONE": "ഒന്ന്",
|
||||
"TWO": "രണ്ട്",
|
||||
"THREE": "മൂന്ന്",
|
||||
"FOUR": "നാല്",
|
||||
"FIVE": "അഞ്ച്",
|
||||
"SIX": "ആറ്",
|
||||
"SEVEN": "ഏഴ്",
|
||||
"EIGHT": "എട്ട്",
|
||||
"NINE": "ഒൻപത്",
|
||||
"TEN": "പത്ത്",
|
||||
"TWELVE": "പന്ത്രണ്ട്",
|
||||
"FIFTEEN": "പതിനഞ്ച്",
|
||||
"TWENTY": "ഇരുപത്",
|
||||
"THIRTY": "മുപ്പത്"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import setNewPassword from './setNewPassword.json';
|
||||
import settings from './settings.json';
|
||||
import signup from './signup.json';
|
||||
import sla from './sla.json';
|
||||
import snooze from './snooze.json';
|
||||
import teamsSettings from './teamsSettings.json';
|
||||
import whatsappTemplates from './whatsappTemplates.json';
|
||||
|
||||
@@ -72,6 +73,7 @@ export default {
|
||||
...settings,
|
||||
...signup,
|
||||
...sla,
|
||||
...snooze,
|
||||
...teamsSettings,
|
||||
...whatsappTemplates,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"SNOOZE_PARSER": {
|
||||
"UNITS": {
|
||||
"MINUTE": "minuto",
|
||||
"MINUTES": "minutos",
|
||||
"HOUR": "hora",
|
||||
"HOURS": "horas",
|
||||
"DAY": "dia",
|
||||
"DAYS": "dias",
|
||||
"WEEK": "semana",
|
||||
"WEEKS": "semanas",
|
||||
"MONTH": "mês",
|
||||
"MONTHS": "meses",
|
||||
"YEAR": "ano",
|
||||
"YEARS": "anos"
|
||||
},
|
||||
"HALF": "meia",
|
||||
"NEXT": "próximo",
|
||||
"THIS": "este",
|
||||
"AT": "às",
|
||||
"IN": "em",
|
||||
"FROM_NOW": "a partir de agora",
|
||||
"NEXT_YEAR": "próximo ano",
|
||||
"MERIDIEM": {
|
||||
"AM": "am",
|
||||
"PM": "pm"
|
||||
},
|
||||
"RELATIVE": {
|
||||
"TOMORROW": "amanhã",
|
||||
"DAY_AFTER_TOMORROW": "depois de amanhã",
|
||||
"NEXT_WEEK": "próxima semana",
|
||||
"NEXT_MONTH": "próximo mês",
|
||||
"THIS_WEEKEND": "este fim de semana",
|
||||
"NEXT_WEEKEND": "próximo fim de semana"
|
||||
},
|
||||
"TIME_OF_DAY": {
|
||||
"MORNING": "manhã",
|
||||
"AFTERNOON": "tarde",
|
||||
"EVENING": "noite",
|
||||
"NIGHT": "noite",
|
||||
"NOON": "meio-dia",
|
||||
"MIDNIGHT": "meia-noite"
|
||||
},
|
||||
"WORD_NUMBERS": {
|
||||
"ONE": "um",
|
||||
"TWO": "dois",
|
||||
"THREE": "três",
|
||||
"FOUR": "quatro",
|
||||
"FIVE": "cinco",
|
||||
"SIX": "seis",
|
||||
"SEVEN": "sete",
|
||||
"EIGHT": "oito",
|
||||
"NINE": "nove",
|
||||
"TEN": "dez",
|
||||
"TWELVE": "doze",
|
||||
"FIFTEEN": "quinze",
|
||||
"TWENTY": "vinte",
|
||||
"THIRTY": "trinta"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { ref, computed, watchEffect, onMounted } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useLocale } from 'shared/composables/useLocale';
|
||||
import { useAppearanceHotKeys } from 'dashboard/composables/commands/useAppearanceHotKeys';
|
||||
import { useInboxHotKeys } from 'dashboard/composables/commands/useInboxHotKeys';
|
||||
import { useGoToCommandHotKeys } from 'dashboard/composables/commands/useGoToCommandHotKeys';
|
||||
@@ -21,7 +22,8 @@ import {
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
const { t, tm } = useI18n();
|
||||
const { resolvedLocale } = useLocale();
|
||||
|
||||
const ninjakeys = ref(null);
|
||||
|
||||
@@ -43,6 +45,8 @@ const SNOOZE_PARENT_IDS = [
|
||||
];
|
||||
const DYNAMIC_SNOOZE_PREFIX = 'dynamic_snooze_';
|
||||
|
||||
const CUSTOM_SNOOZE = wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME;
|
||||
|
||||
const dynamicSnoozeActions = ref([]);
|
||||
const currentCommandRoot = ref(null);
|
||||
|
||||
@@ -77,8 +81,17 @@ const SNOOZE_SECTION_MAP = {
|
||||
bulk_action_snooze_conversation: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
|
||||
};
|
||||
|
||||
const snoozeTranslations = computed(() => {
|
||||
const raw = tm('SNOOZE_PARSER');
|
||||
if (!raw || typeof raw !== 'object') return {};
|
||||
return JSON.parse(JSON.stringify(raw));
|
||||
});
|
||||
|
||||
const buildDynamicSnoozeActions = (search, parentId) => {
|
||||
const suggestions = generateSnoozeSuggestions(search);
|
||||
const suggestions = generateSnoozeSuggestions(search, new Date(), {
|
||||
translations: snoozeTranslations.value,
|
||||
locale: resolvedLocale.value,
|
||||
});
|
||||
if (!suggestions.length) return [];
|
||||
|
||||
const busEvent = SNOOZE_EVENT_MAP[parentId];
|
||||
@@ -98,6 +111,11 @@ const buildDynamicSnoozeActions = (search, parentId) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const resetSnoozeState = () => {
|
||||
currentCommandRoot.value = null;
|
||||
dynamicSnoozeActions.value = [];
|
||||
};
|
||||
|
||||
const patchNinjaKeysOpenClose = el => {
|
||||
if (!el || typeof el.open !== 'function' || typeof el.close !== 'function') {
|
||||
return;
|
||||
@@ -114,8 +132,7 @@ const patchNinjaKeysOpenClose = el => {
|
||||
};
|
||||
|
||||
el.close = (...args) => {
|
||||
currentCommandRoot.value = null;
|
||||
dynamicSnoozeActions.value = [];
|
||||
resetSnoozeState();
|
||||
return originalClose(...args);
|
||||
};
|
||||
};
|
||||
@@ -126,23 +143,14 @@ const onSelected = item => {
|
||||
action: { title = null, section = null, id = null, children = null } = {},
|
||||
} = {},
|
||||
} = item;
|
||||
// Added this condition to prevent setting the selectedSnoozeType to null
|
||||
// When we select the "custom snooze" (CMD bar will close and the custom snooze modal will open)
|
||||
if (id === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
|
||||
selectedSnoozeType.value = wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME;
|
||||
} else {
|
||||
selectedSnoozeType.value = null;
|
||||
}
|
||||
|
||||
selectedSnoozeType.value = id === CUSTOM_SNOOZE ? id : null;
|
||||
|
||||
if (Array.isArray(children) && children.length) {
|
||||
currentCommandRoot.value = id;
|
||||
}
|
||||
|
||||
useTrack(GENERAL_EVENTS.COMMAND_BAR, {
|
||||
section,
|
||||
action: title,
|
||||
});
|
||||
|
||||
useTrack(GENERAL_EVENTS.COMMAND_BAR, { section, action: title });
|
||||
setCommandBarData();
|
||||
};
|
||||
|
||||
@@ -176,15 +184,10 @@ const onCommandBarChange = item => {
|
||||
};
|
||||
|
||||
const onClosed = () => {
|
||||
// If the selectedSnoozeType is not "SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME (custom snooze)" then we set the context menu chat id to null
|
||||
// Else we do nothing and its handled in the ChatList.vue hideCustomSnoozeModal() method
|
||||
if (
|
||||
selectedSnoozeType.value !== wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME
|
||||
) {
|
||||
if (selectedSnoozeType.value !== CUSTOM_SNOOZE) {
|
||||
store.dispatch('setContextMenuChatId', null);
|
||||
}
|
||||
currentCommandRoot.value = null;
|
||||
dynamicSnoozeActions.value = [];
|
||||
resetSnoozeState();
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user