fix(i18n): add zh_TW snooze parser locale (#13822)
This commit is contained in:
@@ -166,6 +166,8 @@ const TOD_TO_MERIDIEM = {
|
|||||||
evening: 'pm',
|
evening: 'pm',
|
||||||
night: 'pm',
|
night: 'pm',
|
||||||
};
|
};
|
||||||
|
const CJK_CHAR_RE =
|
||||||
|
/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
||||||
|
|
||||||
// ─── Translation Cache ──────────────────────────────────────────────────────
|
// ─── Translation Cache ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -278,8 +280,13 @@ const escapeRegex = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|||||||
const substituteLocalTokens = (text, pairs) => {
|
const substituteLocalTokens = (text, pairs) => {
|
||||||
let r = text;
|
let r = text;
|
||||||
pairs.forEach(([local, en]) => {
|
pairs.forEach(([local, en]) => {
|
||||||
const re = new RegExp(`(?<=^|\\s)${escapeRegex(local)}(?=\\s|$)`, 'g');
|
if (CJK_CHAR_RE.test(local)) {
|
||||||
r = r.replace(re, en);
|
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;
|
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 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_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(
|
const DURATION_FROM_NOW_RE = new RegExp(
|
||||||
`^${NUM_RE}\\s+${UNIT_RE}\\s+from\\s+now$`
|
`^${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(
|
const RELATIVE_DAY_TOD_RE = new RegExp(
|
||||||
`^(${RELATIVE_DAYS})\\s+(?:at\\s+)?(${TIME_OF_DAY_NAMES})$`
|
`^(${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(
|
const RELATIVE_DAY_TOD_TIME_RE = new RegExp(
|
||||||
`^(${RELATIVE_DAYS})\\s+(?:at\\s+)?(${TIME_OF_DAY_NAMES})\\s+(\\d{1,2}(?::\\d{2})?)$`
|
`^(${RELATIVE_DAYS})\\s+(?:at\\s+)?(${TIME_OF_DAY_NAMES})\\s+(\\d{1,2}(?::\\d{2})?)$`
|
||||||
);
|
);
|
||||||
@@ -245,6 +251,7 @@ const matchDuration = (text, now) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
parseDuration(text.match(DURATION_FROM_NOW_RE), now) ||
|
parseDuration(text.match(DURATION_FROM_NOW_RE), now) ||
|
||||||
|
parseDuration(text.match(RELATIVE_DURATION_AFTER_RE), now) ||
|
||||||
parseDuration(text.match(RELATIVE_DURATION_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);
|
const dayAtTimeMatch = text.match(RELATIVE_DAY_AT_TIME_RE);
|
||||||
if (dayAtTimeMatch) {
|
if (dayAtTimeMatch) {
|
||||||
const [, dayKey, timeRaw] = 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', () => {
|
describe('P1: short non-English tokens must NOT produce spurious half-duration suggestions', () => {
|
||||||
it('Arabic "غد" does not produce half-duration suggestions', () => {
|
it('Arabic "غد" does not produce half-duration suggestions', () => {
|
||||||
const results = generateDateSuggestions('غد', now, {
|
const results = generateDateSuggestions('غد', now, {
|
||||||
@@ -1721,6 +1739,37 @@ describe('generateDateSuggestions — localized input regressions', () => {
|
|||||||
expect(results[0].date.getHours()).toBe(6);
|
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', () => {
|
describe('no-space duration suggestions', () => {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import setNewPassword from './setNewPassword.json';
|
|||||||
import settings from './settings.json';
|
import settings from './settings.json';
|
||||||
import signup from './signup.json';
|
import signup from './signup.json';
|
||||||
import sla from './sla.json';
|
import sla from './sla.json';
|
||||||
|
import snooze from './snooze.json';
|
||||||
import teamsSettings from './teamsSettings.json';
|
import teamsSettings from './teamsSettings.json';
|
||||||
import whatsappTemplates from './whatsappTemplates.json';
|
import whatsappTemplates from './whatsappTemplates.json';
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ export default {
|
|||||||
...settings,
|
...settings,
|
||||||
...signup,
|
...signup,
|
||||||
...sla,
|
...sla,
|
||||||
|
...snooze,
|
||||||
...teamsSettings,
|
...teamsSettings,
|
||||||
...whatsappTemplates,
|
...whatsappTemplates,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,72 +1,72 @@
|
|||||||
{
|
{
|
||||||
"SNOOZE_PARSER": {
|
"SNOOZE_PARSER": {
|
||||||
"UNITS": {
|
"UNITS": {
|
||||||
"MINUTE": "minute",
|
"MINUTE": "分鐘",
|
||||||
"MINUTES": "minutes",
|
"MINUTES": "分鐘",
|
||||||
"HOUR": "hour",
|
"HOUR": "小時",
|
||||||
"HOURS": "小時",
|
"HOURS": "小時",
|
||||||
"DAY": "day",
|
"DAY": "天",
|
||||||
"DAYS": "days",
|
"DAYS": "天",
|
||||||
"WEEK": "week",
|
"WEEK": "週",
|
||||||
"WEEKS": "weeks",
|
"WEEKS": "週",
|
||||||
"MONTH": "month",
|
"MONTH": "月",
|
||||||
"MONTHS": "months",
|
"MONTHS": "月",
|
||||||
"YEAR": "month",
|
"YEAR": "年",
|
||||||
"YEARS": "years"
|
"YEARS": "年"
|
||||||
},
|
},
|
||||||
"HALF": "half",
|
"HALF": "半",
|
||||||
"NEXT": "next",
|
"NEXT": "下一個",
|
||||||
"THIS": "this",
|
"THIS": "這個",
|
||||||
"AT": "at",
|
"AT": "在",
|
||||||
"IN": "in",
|
"IN": "在",
|
||||||
"FROM_NOW": "from now",
|
"FROM_NOW": "之後",
|
||||||
"NEXT_YEAR": "next year",
|
"NEXT_YEAR": "明年",
|
||||||
"MERIDIEM": {
|
"MERIDIEM": {
|
||||||
"AM": "am",
|
"AM": "上午",
|
||||||
"PM": "pm"
|
"PM": "下午"
|
||||||
},
|
},
|
||||||
"RELATIVE": {
|
"RELATIVE": {
|
||||||
"TOMORROW": "明天",
|
"TOMORROW": "明天",
|
||||||
"DAY_AFTER_TOMORROW": "day after tomorrow",
|
"DAY_AFTER_TOMORROW": "後天",
|
||||||
"NEXT_WEEK": "下週",
|
"NEXT_WEEK": "下週",
|
||||||
"NEXT_MONTH": "next month",
|
"NEXT_MONTH": "下個月",
|
||||||
"THIS_WEEKEND": "this weekend",
|
"THIS_WEEKEND": "這個週末",
|
||||||
"NEXT_WEEKEND": "next weekend"
|
"NEXT_WEEKEND": "下個週末"
|
||||||
},
|
},
|
||||||
"TIME_OF_DAY": {
|
"TIME_OF_DAY": {
|
||||||
"MORNING": "morning",
|
"MORNING": "早上",
|
||||||
"AFTERNOON": "afternoon",
|
"AFTERNOON": "下午",
|
||||||
"EVENING": "evening",
|
"EVENING": "晚上",
|
||||||
"NIGHT": "night",
|
"NIGHT": "夜晚",
|
||||||
"NOON": "noon",
|
"NOON": "中午",
|
||||||
"MIDNIGHT": "midnight"
|
"MIDNIGHT": "午夜"
|
||||||
},
|
},
|
||||||
"WORD_NUMBERS": {
|
"WORD_NUMBERS": {
|
||||||
"ONE": "one",
|
"ONE": "一",
|
||||||
"TWO": "two",
|
"TWO": "二",
|
||||||
"THREE": "three",
|
"THREE": "三",
|
||||||
"FOUR": "four",
|
"FOUR": "四",
|
||||||
"FIVE": "five",
|
"FIVE": "五",
|
||||||
"SIX": "six",
|
"SIX": "六",
|
||||||
"SEVEN": "seven",
|
"SEVEN": "七",
|
||||||
"EIGHT": "eight",
|
"EIGHT": "八",
|
||||||
"NINE": "nine",
|
"NINE": "九",
|
||||||
"TEN": "ten",
|
"TEN": "十",
|
||||||
"TWELVE": "twelve",
|
"TWELVE": "十二",
|
||||||
"FIFTEEN": "fifteen",
|
"FIFTEEN": "十五",
|
||||||
"TWENTY": "twenty",
|
"TWENTY": "二十",
|
||||||
"THIRTY": "thirty"
|
"THIRTY": "三十"
|
||||||
},
|
},
|
||||||
"ORDINALS": {
|
"ORDINALS": {
|
||||||
"FIRST": "first",
|
"FIRST": "第一",
|
||||||
"SECOND": "second",
|
"SECOND": "第二",
|
||||||
"THIRD": "third",
|
"THIRD": "第三",
|
||||||
"FOURTH": "fourth",
|
"FOURTH": "第四",
|
||||||
"FIFTH": "fifth"
|
"FIFTH": "第五"
|
||||||
},
|
},
|
||||||
"OF": "of",
|
"OF": "的",
|
||||||
"AFTER": "after",
|
"AFTER": "後",
|
||||||
"WEEK": "week",
|
"WEEK": "週",
|
||||||
"DAY": "day"
|
"DAY": "天"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user