fix(i18n): add zh_TW snooze parser locale (#13822)

This commit is contained in:
salmonumbrella
2026-03-25 04:24:18 -07:00
committed by GitHub
parent 775b73d1f9
commit 6ff643b045
5 changed files with 127 additions and 55 deletions

View File

@@ -166,6 +166,8 @@ const TOD_TO_MERIDIEM = {
evening: 'pm',
night: 'pm',
};
const CJK_CHAR_RE =
/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
// ─── Translation Cache ──────────────────────────────────────────────────────
@@ -278,8 +280,13 @@ const escapeRegex = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const substituteLocalTokens = (text, pairs) => {
let r = text;
pairs.forEach(([local, en]) => {
const re = new RegExp(`(?<=^|\\s)${escapeRegex(local)}(?=\\s|$)`, 'g');
r = r.replace(re, en);
if (CJK_CHAR_RE.test(local)) {
const re = new RegExp(escapeRegex(local), 'g');
r = r.replace(re, ` ${en} `);
} else {
const re = new RegExp(`(?<=^|\\s)${escapeRegex(local)}(?=\\s|$)`, 'g');
r = r.replace(re, en);
}
});
return r;
};

View File

@@ -82,6 +82,9 @@ const ORDINAL_RE = `(\\d{1,2}(?:st|nd|rd|th)?|${ORDINAL_WORDS})`;
const HALF_UNIT_RE = /^(?:in\s+)?half\s+(?:an?\s+)?(hour|day|week|month|year)$/;
const RELATIVE_DURATION_RE = new RegExp(`^(?:in\\s+)?${NUM_RE}\\s+${UNIT_RE}$`);
const RELATIVE_DURATION_AFTER_RE = new RegExp(
`^(?:in\\s+)?${NUM_RE}\\s+${UNIT_RE}\\s+after$`
);
const DURATION_FROM_NOW_RE = new RegExp(
`^${NUM_RE}\\s+${UNIT_RE}\\s+from\\s+now$`
);
@@ -89,6 +92,9 @@ const RELATIVE_DAY_ONLY_RE = new RegExp(`^(${RELATIVE_DAYS})$`);
const RELATIVE_DAY_TOD_RE = new RegExp(
`^(${RELATIVE_DAYS})\\s+(?:at\\s+)?(${TIME_OF_DAY_NAMES})$`
);
const RELATIVE_DAY_MERIDIEM_RE = new RegExp(
`^(${RELATIVE_DAYS})\\s+(?:at\\s+)?(am|pm)$`
);
const RELATIVE_DAY_TOD_TIME_RE = new RegExp(
`^(${RELATIVE_DAYS})\\s+(?:at\\s+)?(${TIME_OF_DAY_NAMES})\\s+(\\d{1,2}(?::\\d{2})?)$`
);
@@ -245,6 +251,7 @@ const matchDuration = (text, now) => {
return (
parseDuration(text.match(DURATION_FROM_NOW_RE), now) ||
parseDuration(text.match(RELATIVE_DURATION_AFTER_RE), now) ||
parseDuration(text.match(RELATIVE_DURATION_RE), now)
);
};
@@ -303,6 +310,13 @@ const matchRelativeDay = (text, now) => {
);
}
const dayMeridiemMatch = text.match(RELATIVE_DAY_MERIDIEM_RE);
if (dayMeridiemMatch) {
const [, dayKey, meridiem] = dayMeridiemMatch;
const hours = meridiem === 'am' ? 9 : 14;
return applyTimeWithRollover(RELATIVE_DAY_MAP[dayKey], hours, 0, now);
}
const dayAtTimeMatch = text.match(RELATIVE_DAY_AT_TIME_RE);
if (dayAtTimeMatch) {
const [, dayKey, timeRaw] = dayAtTimeMatch;

View File

@@ -1626,6 +1626,24 @@ describe('generateDateSuggestions — localized input regressions', () => {
},
};
const zhTWSnoozeTranslations = {
UNITS: {
HOUR: '小時',
HOURS: '小時',
DAY: '天',
DAYS: '天',
},
HALF: '半',
RELATIVE: {
TOMORROW: '明天',
},
MERIDIEM: {
AM: '上午',
PM: '下午',
},
AFTER: '後',
};
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, {
@@ -1721,6 +1739,37 @@ describe('generateDateSuggestions — localized input regressions', () => {
expect(results[0].date.getHours()).toBe(6);
});
});
describe('zh_TW compact CJK inputs', () => {
const options = {
translations: zhTWSnoozeTranslations,
locale: 'zh-TW',
};
it('parses "2小時後" (2 hours from now) without spaces', () => {
const results = generateDateSuggestions('2小時後', now, options);
expect(results.length).toBeGreaterThan(0);
expect(results[0].date.getDate()).toBe(16);
expect(results[0].date.getHours()).toBe(12);
expect(results[0].date.getMinutes()).toBe(0);
});
it('parses "半天" (half day) without spaces', () => {
const results = generateDateSuggestions('半天', now, options);
expect(results.length).toBeGreaterThan(0);
expect(results[0].date.getDate()).toBe(16);
expect(results[0].date.getHours()).toBe(22);
expect(results[0].date.getMinutes()).toBe(0);
});
it('parses "明天 上午" (tomorrow AM) into tomorrow 9am', () => {
const results = generateDateSuggestions('明天 上午', now, options);
expect(results.length).toBeGreaterThan(0);
expect(results[0].date.getDate()).toBe(17);
expect(results[0].date.getHours()).toBe(9);
expect(results[0].date.getMinutes()).toBe(0);
});
});
});
describe('no-space duration suggestions', () => {