fix(i18n): add zh_TW snooze parser locale (#13822)
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -74,6 +75,7 @@ export default {
|
||||
...settings,
|
||||
...signup,
|
||||
...sla,
|
||||
...snooze,
|
||||
...teamsSettings,
|
||||
...whatsappTemplates,
|
||||
};
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
{
|
||||
"SNOOZE_PARSER": {
|
||||
"UNITS": {
|
||||
"MINUTE": "minute",
|
||||
"MINUTES": "minutes",
|
||||
"HOUR": "hour",
|
||||
"MINUTE": "分鐘",
|
||||
"MINUTES": "分鐘",
|
||||
"HOUR": "小時",
|
||||
"HOURS": "小時",
|
||||
"DAY": "day",
|
||||
"DAYS": "days",
|
||||
"WEEK": "week",
|
||||
"WEEKS": "weeks",
|
||||
"MONTH": "month",
|
||||
"MONTHS": "months",
|
||||
"YEAR": "month",
|
||||
"YEARS": "years"
|
||||
"DAY": "天",
|
||||
"DAYS": "天",
|
||||
"WEEK": "週",
|
||||
"WEEKS": "週",
|
||||
"MONTH": "月",
|
||||
"MONTHS": "月",
|
||||
"YEAR": "年",
|
||||
"YEARS": "年"
|
||||
},
|
||||
"HALF": "half",
|
||||
"NEXT": "next",
|
||||
"THIS": "this",
|
||||
"AT": "at",
|
||||
"IN": "in",
|
||||
"FROM_NOW": "from now",
|
||||
"NEXT_YEAR": "next year",
|
||||
"HALF": "半",
|
||||
"NEXT": "下一個",
|
||||
"THIS": "這個",
|
||||
"AT": "在",
|
||||
"IN": "在",
|
||||
"FROM_NOW": "之後",
|
||||
"NEXT_YEAR": "明年",
|
||||
"MERIDIEM": {
|
||||
"AM": "am",
|
||||
"PM": "pm"
|
||||
"AM": "上午",
|
||||
"PM": "下午"
|
||||
},
|
||||
"RELATIVE": {
|
||||
"TOMORROW": "明天",
|
||||
"DAY_AFTER_TOMORROW": "day after tomorrow",
|
||||
"DAY_AFTER_TOMORROW": "後天",
|
||||
"NEXT_WEEK": "下週",
|
||||
"NEXT_MONTH": "next month",
|
||||
"THIS_WEEKEND": "this weekend",
|
||||
"NEXT_WEEKEND": "next weekend"
|
||||
"NEXT_MONTH": "下個月",
|
||||
"THIS_WEEKEND": "這個週末",
|
||||
"NEXT_WEEKEND": "下個週末"
|
||||
},
|
||||
"TIME_OF_DAY": {
|
||||
"MORNING": "morning",
|
||||
"AFTERNOON": "afternoon",
|
||||
"EVENING": "evening",
|
||||
"NIGHT": "night",
|
||||
"NOON": "noon",
|
||||
"MIDNIGHT": "midnight"
|
||||
"MORNING": "早上",
|
||||
"AFTERNOON": "下午",
|
||||
"EVENING": "晚上",
|
||||
"NIGHT": "夜晚",
|
||||
"NOON": "中午",
|
||||
"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"
|
||||
"ONE": "一",
|
||||
"TWO": "二",
|
||||
"THREE": "三",
|
||||
"FOUR": "四",
|
||||
"FIVE": "五",
|
||||
"SIX": "六",
|
||||
"SEVEN": "七",
|
||||
"EIGHT": "八",
|
||||
"NINE": "九",
|
||||
"TEN": "十",
|
||||
"TWELVE": "十二",
|
||||
"FIFTEEN": "十五",
|
||||
"TWENTY": "二十",
|
||||
"THIRTY": "三十"
|
||||
},
|
||||
"ORDINALS": {
|
||||
"FIRST": "first",
|
||||
"SECOND": "second",
|
||||
"THIRD": "third",
|
||||
"FOURTH": "fourth",
|
||||
"FIFTH": "fifth"
|
||||
"FIRST": "第一",
|
||||
"SECOND": "第二",
|
||||
"THIRD": "第三",
|
||||
"FOURTH": "第四",
|
||||
"FIFTH": "第五"
|
||||
},
|
||||
"OF": "of",
|
||||
"AFTER": "after",
|
||||
"WEEK": "week",
|
||||
"DAY": "day"
|
||||
"OF": "的",
|
||||
"AFTER": "後",
|
||||
"WEEK": "週",
|
||||
"DAY": "天"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user