diff --git a/frontend/src/components/CalendarSubscriptionModal.vue b/frontend/src/components/CalendarSubscriptionModal.vue
index e1791c4..7657fe7 100644
--- a/frontend/src/components/CalendarSubscriptionModal.vue
+++ b/frontend/src/components/CalendarSubscriptionModal.vue
@@ -79,16 +79,16 @@ async function copyUrl() {
{{ tr('快速配置指南', 'Quick Setup Guide') }}
- 🍎 {{ tr('Apple 日历 (Mac / iOS)', 'Apple Calendar (Mac / iOS)') }}
- {{ tr('文件 ➔ 新建日历订阅 ➔ 粘贴上方链接 ➔ 自动刷新建议选择“每小时”。', 'File ➔ New Calendar Subscription ➔ Paste URL ➔ Set Auto-refresh to Every Hour.') }}
+ {{ tr('Apple 日历 (Mac / iOS)', 'Apple Calendar (Mac / iOS)') }}
+ {{ tr('文件 / 新建日历订阅 / 粘贴上方链接 / 自动刷新建议选择“每小时”。', 'File / New Calendar Subscription / Paste URL / Set Auto-refresh to Every Hour.') }}
- 📅 Google Calendar
- {{ tr('其他日历旁点击 “+” ➔ 通过网址添加 ➔ 粘贴链接 ➔ 添加日历。', 'Click "+" next to Other calendars ➔ From URL ➔ Paste URL ➔ Add calendar.') }}
+ Google Calendar
+ {{ tr('其他日历旁点击 “+” / 通过网址添加 / 粘贴链接 / 添加日历。', 'Click "+" next to Other calendars / From URL / Paste URL / Add calendar.') }}
- 📧 Microsoft Outlook
- {{ tr('添加日历 ➔ 从 Web 订阅 ➔ 粘贴链接并保存。', 'Add calendar ➔ Subscribe from web ➔ Paste URL and save.') }}
+ Microsoft Outlook
+ {{ tr('添加日历 / 从 Web 订阅 / 粘贴链接并保存。', 'Add calendar / Subscribe from web / Paste URL and save.') }}
diff --git a/frontend/src/components/WebhookNotifyModal.vue b/frontend/src/components/WebhookNotifyModal.vue
index 4ad8439..2f1adb0 100644
--- a/frontend/src/components/WebhookNotifyModal.vue
+++ b/frontend/src/components/WebhookNotifyModal.vue
@@ -80,7 +80,7 @@ async function sendNotification() {
{{ tr('推送内容预览', 'Card Preview') }}
-
📋 {{ tr('会议纪要:', 'Meeting Summary: ') }}{{ meeting.title }}
+
{{ tr('会议纪要:', 'Meeting Summary: ') }}{{ meeting.title }}
{{ tr('状态:', 'Status: ') }}{{ meeting.status === 'completed' ? tr('已完成', 'Completed') : tr('进行中', 'In Progress') }} ·
{{ tr('议题数:', 'Agendas: ') }}{{ meeting.agenda_items.length }}
diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts
index d613c16..9883863 100644
--- a/frontend/src/i18n/index.ts
+++ b/frontend/src/i18n/index.ts
@@ -231,15 +231,15 @@ export const SEEDED_ZH_TO_EN: Record = {
'聚焦会前准备、会中专注研讨与会后归档闭环。':
'Focus on pre-meeting prep, live focused discussion, and post-meeting archive loop.',
'Q3 架构演进与中英双语体验评审': 'Q3 Architecture Evolution & Bilingual UX Review',
- '评审页内 ✨ AI 智能提炼、双向行动项状态机及中英双语无缝切换体验。':
- 'Review in-place ✨ AI Smart Extract, two-way action state machine, and seamless bilingual switching.',
- '页内 ✨ AI 智能提炼与结构化产出': 'In-place ✨ AI Smart Extract & Structured Outcomes',
+ '评审页内 AI 智能提炼、双向行动项状态机及中英双语无缝切换体验。':
+ 'Review in-place AI Smart Extract, two-way action state machine, and seamless bilingual switching.',
+ '页内 AI 智能提炼与结构化产出': 'In-place AI Smart Extract & Structured Outcomes',
'围绕会议记录与 AI 提炼流程达成以下结论:':
'Key conclusions on meeting notes and AI extraction workflow:',
- '团队一致决定采用页内 ✨ AI 智能提炼直接写入当前议题记录,彻底移除冗余弹窗':
- 'Unanimously decided to use in-place ✨ AI Smart Extract directly in agenda notes, removing redundant modals',
- '采用页内 ✨ AI 智能提炼直接写入当前议题记录,彻底移除冗余弹窗':
- 'Use in-place ✨ AI Smart Extract directly in agenda notes, removing redundant modals',
+ '团队一致决定采用页内 AI 智能提炼直接写入当前议题记录,彻底移除冗余弹窗':
+ 'Unanimously decided to use in-place AI Smart Extract directly in agenda notes, removing redundant modals',
+ '采用页内 AI 智能提炼直接写入当前议题记录,彻底移除冗余弹窗':
+ 'Use in-place AI Smart Extract directly in agenda notes, removing redundant modals',
'请 @admin 完善中英双语标签 (@决策/@Decision、@行动/@Action) 解析与单元测试':
'Please @admin refine bilingual tag (@Decision, @Action) parsing and unit tests',
'完善中英双语标签 (@决策/@Decision、@行动/@Action) 解析与单元测试':
@@ -266,8 +266,8 @@ export const SEEDED_ZH_TO_EN: Record = {
'周会例行同步': 'Weekly Sync',
'双周架构评审': 'Bi-weekly Architecture Review',
'每周三 10:00 固定同步': 'Every Wednesday at 10:00',
- '确认采用页内 ✨ AI 智能提炼直接写入当前议题记录,彻底移除冗余弹窗':
- 'Confirm in-place ✨ AI Smart Extract directly in agenda notes, removing redundant modals',
+ '确认采用页内 AI 智能提炼直接写入当前议题记录,彻底移除冗余弹窗':
+ 'Confirm in-place AI Smart Extract directly in agenda notes, removing redundant modals',
}
export const SEEDED_EN_TO_ZH: Record = Object.fromEntries(
@@ -279,7 +279,8 @@ const SORTED_EN_ENTRIES = Object.entries(SEEDED_EN_TO_ZH).sort((a, b) => b[0].le
export function translateSeededText(text: string, lang: Locale = locale.value): string {
if (!text) return text
- const trimmed = text.trim()
+ const cleaned = text.replace(/\u2728\s*/g, '')
+ const trimmed = cleaned.trim()
if (lang === 'en') {
if (SEEDED_ZH_TO_EN[trimmed]) return SEEDED_ZH_TO_EN[trimmed]
const discussMatch = trimmed.match(/^###\s+(.+?)\s+·\s+讨论要点$/)
@@ -310,7 +311,7 @@ export function translateSeededText(text: string, lang: Locale = locale.value):
if (qFallbackMatch) {
return `Cross-module boundary details for "${translateSeededText(qFallbackMatch[1], 'en')}" pending final confirmation`
}
- let replaced = text
+ let replaced = cleaned
for (const [zh, en] of SORTED_ZH_ENTRIES) {
if (zh.length >= 4 && replaced.includes(zh)) {
replaced = replaced.split(zh).join(en)
@@ -348,7 +349,7 @@ export function translateSeededText(text: string, lang: Locale = locale.value):
if (enQFallbackMatch) {
return `「${translateSeededText(enQFallbackMatch[1], 'zh')}」跨模块边界细节待最终确认`
}
- let replaced = text
+ let replaced = cleaned
for (const [en, zh] of SORTED_EN_ENTRIES) {
if (en.length >= 6 && replaced.includes(en)) {
replaced = replaced.split(en).join(zh)
diff --git a/frontend/src/tests/focus-and-integrations.test.ts b/frontend/src/tests/focus-and-integrations.test.ts
index 0f078b0..e97b1ea 100644
--- a/frontend/src/tests/focus-and-integrations.test.ts
+++ b/frontend/src/tests/focus-and-integrations.test.ts
@@ -311,7 +311,7 @@ describe('P0 Features: Bidirectional Actions, Focus Mode, Calendar & Webhooks',
expect(screen.getByLabelText('议题记录')).toHaveValue('正在输入的未保存笔记内容')
})
- it('structures and extracts decisions, actions, and open questions in-place via ✨ AI 智能提炼', async () => {
+ it('structures and extracts decisions, actions, and open questions in-place via AI 智能提炼', async () => {
const meeting = meetingFixture()
apiMock.mockImplementation((url: string, init?: RequestInit) => {
if (url === '/api/v1/meetings/m1/ingest' && init?.method === 'POST') {
diff --git a/frontend/src/tests/ui-foundation.test.ts b/frontend/src/tests/ui-foundation.test.ts
index d3b1b86..faedeff 100644
--- a/frontend/src/tests/ui-foundation.test.ts
+++ b/frontend/src/tests/ui-foundation.test.ts
@@ -316,7 +316,7 @@ describe('icon glyph policy', () => {
it('uses lucide svg icons instead of unicode glyphs in the source tree', () => {
// Spec §4.3: functional icons must be Lucide SVG wrapped in NIcon; the brand "M"
// mark (a logo, not a functional icon) is explicitly kept and is not in this list.
- const forbidden = ['⌕', '•••', '✓', '○', '→', '⌁', '⌄', '×', '✕', '✦', '◎', '●']
+ const forbidden = ['⌕', '•••', '✓', '○', '→', '⌁', '⌄', '×', '✕', '✦', '◎', '●', '📋', '🍎', '📅', '📧', '✨']
const sources = import.meta.glob(['../**/*.vue', '../**/*.ts', '../**/*.css'], {
query: '?raw',
import: 'default',