Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions app/shared/src/chatview/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,29 @@ export const chatviewResources = {
'oidc.loginSuccess.heading': '登录成功',
'oidc.loginSuccess.body': '登录流程已完成。',
'oidc.loginSuccess.closeHint': '您可以关闭此页面并返回 AgentHub 桌面应用。',

// ── Shared UI preview surfaces (#2154 UIUX lane i18n pass) ──
'preview.parsing': '正在解析文档...',
'preview.parsingSlideshow': '正在解析演示文稿...',
'preview.parsingTable': '正在解析表格...',
'preview.retry': '重试',
'preview.blankSlide': '空白幻灯片',
'preview.emptySheet': '空工作表',
'filePreview.imageViaFileUrl': '图片内容将通过文件 URL 加载',
'inspector.runtimeEvidence': '运行证据',
'addContact.qrTitle': 'TokenDance 企业二维码',
'addContact.qrDescription': '对方扫码后可申请加入企业,管理员确认后出现在组织内联系人。',
'addContact.inviteLink': '邀请链接',
'addContact.copyLink': '复制链接',
'addContact.inviteLinkDescription': '适合发给已在 TokenDance 协作空间内的同事。',
'addContact.inviteCodeHint': '企业邀请码 24 小时内有效,可通过 IM 或邮件发送。',
'addContact.copyInviteCode': '复制邀请码',
'addContact.phone': '手机号',
'addContact.phonePlaceholder': '输入手机号',
'addContact.remark': '备注',
'addContact.sendInvite': '发送邀请',
'addContact.title': '添加联系人',
'addContact.subtitle': '邀请企业成员加入 TokenDance,或添加外部联系人用于项目协作。',
},

en: {
Expand Down Expand Up @@ -1681,5 +1704,27 @@ export const chatviewResources = {
'oidc.loginSuccess.heading': 'Login Successful',
'oidc.loginSuccess.body': 'The login flow has completed.',
'oidc.loginSuccess.closeHint': 'You may close this page and return to the AgentHub desktop app.',
// ── Shared UI preview surfaces (#2154 UIUX lane i18n pass) ──
'preview.parsing': 'Parsing document...',
'preview.parsingSlideshow': 'Parsing slideshow...',
'preview.parsingTable': 'Parsing spreadsheet...',
'preview.retry': 'Retry',
'preview.blankSlide': 'Blank slide',
'preview.emptySheet': 'Empty sheet',
'filePreview.imageViaFileUrl': 'Image content will load via file URL',
'inspector.runtimeEvidence': 'Runtime evidence',
'addContact.qrTitle': 'TokenDance enterprise QR code',
'addContact.qrDescription': 'Scan to apply to join the enterprise; an admin confirms membership into your org contacts.',
'addContact.inviteLink': 'Invite link',
'addContact.copyLink': 'Copy link',
'addContact.inviteLinkDescription': 'Best for colleagues already in a TokenDance workspace.',
'addContact.inviteCodeHint': 'Enterprise invite codes are valid for 24 hours and can be sent via IM or email.',
'addContact.copyInviteCode': 'Copy invite code',
'addContact.phone': 'Phone number',
'addContact.phonePlaceholder': 'Enter phone number',
'addContact.remark': 'Remark',
'addContact.sendInvite': 'Send invite',
'addContact.title': 'Add contact',
'addContact.subtitle': 'Invite teammates to TokenDance, or add external contacts for project collaboration.',
},
}
128 changes: 126 additions & 2 deletions app/shared/src/i18n/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ export const sharedWorkbenchResources = {
},
},
settings: {
close: '关闭',
currentSpace: '当前空间',
searchPlaceholder: '搜索设置项',
statusPreview: '状态组件预览',
nav: {
appearance: '外观',
appearanceDesc: '主题、语言和界面风格',
Expand Down Expand Up @@ -209,10 +213,22 @@ export const sharedWorkbenchResources = {
loadFailed: '设置加载失败',
},
projects: {
view: '视图',
announcement: '项目公告',
editAnnouncement: '编辑',
edit: '编辑项目',
projectName: '项目名称',
projectDescription: '项目描述',
themeColor: '主题色',
cancel: '取消',
saving: '保存中',
activeRuns: '活跃运行',
completed: '已完成',
queue: '等待队列',
artifactsPreview: '项目产物预览',
nav: { all: '全部项目', running: '运行中', completed: '已完成', archived: '已归档' },
tab: { overview: '概览', settings: '设置', members: '成员' },
newProject: '新建项目',
edit: '编辑项目',
projectRuns: '项目运行',
loading: '正在加载项目…',
loadMoreError: '加载更多项目失败:{{message}}',
Expand Down Expand Up @@ -250,6 +266,16 @@ export const sharedWorkbenchResources = {
},
},
contacts: {
manage: '管理',
recentContacts: '最近联系人',
tokenDanceGroups: 'TokenDance 群组',
openGroupChat: '打开群聊',
enter: '进入',
loadMore: '加载更多',
filter: '筛选',
newTicket: '新建工单',
add: '添加联系人',
new: { title: '新的联系人', subtitle: '搜索用户、发送好友请求、处理收到的请求。' },
error: {
title: '通讯录加载失败',
description: '请稍后重试;如持续失败请检查 Hub 连接。',
Expand Down Expand Up @@ -278,6 +304,17 @@ export const sharedWorkbenchResources = {
},
},
docs: {
home: '主页',
create: '新建',
deleteConfirm: '确认删除',
cancel: '取消',
colTitle: '标题',
colLocation: '位置',
colOwner: '所有者',
colOpenedAt: '打开时间',
lightPreview: '轻量文档预览',
myLibrary: '我的文档库',
loading: '加载中…',
tab: { recent: '最近访问', mine: '归我所有', shared: '与我共享', starred: '收藏' },
search: { placeholder: '搜索云文档' },
newDoc: '新建文档',
Expand All @@ -300,6 +337,7 @@ export const sharedWorkbenchResources = {
},
},
agents: {
allowConfirmDeny: '允许 / 需确认 / 禁止',
nav: {
installed: '已安装',
market: 'Agent 市场',
Expand Down Expand Up @@ -590,6 +628,30 @@ export const sharedWorkbenchResources = {
},
},
tasks: {
navTasks: '任务',
taskCenter: '跨项目任务中心;项目页只展示当前项目内任务和运行。',
quickAccess: '快速访问',
newGroup: '新建分组',
currentSelection: '当前选中',
statusFilter: '推进状态',
assignedToMe: '指派给我',
groupByProject: '按项目分组',
viewAssigneeTasks: '看负责人任务',
edit: '编辑',
delete: '删除',
save: '保存',
cancel: '取消',
taskTitle: '任务标题',
project: '所属项目',
assignee: '负责人',
startAt: '开始时间',
dueAt: '截止时间',
creator: '创建人',
importTasks: '导入任务',
exportView: '导出当前视图',
manageFields: '管理任务字段',
filter: '筛选',
loadMore: '加载更多',
nav: { all: '全部任务', assigned: '分配给我', created: '我创建的', watching: '我关注的' },
view: { list: '列表', board: '看板', timeline: '时间线' },
newTask: '新建任务',
Expand Down Expand Up @@ -841,6 +903,10 @@ export const sharedWorkbenchResources = {
},
},
settings: {
close: 'Close',
currentSpace: 'Current space',
searchPlaceholder: 'Search settings',
statusPreview: 'Status component preview',
nav: {
appearance: 'Appearance',
appearanceDesc: 'Theme, language and interface style',
Expand Down Expand Up @@ -893,10 +959,22 @@ export const sharedWorkbenchResources = {
loadFailed: 'Settings load failed',
},
projects: {
view: 'View',
announcement: 'Project announcement',
editAnnouncement: 'Edit',
edit: 'Edit project',
projectName: 'Project name',
projectDescription: 'Project description',
themeColor: 'Theme color',
cancel: 'Cancel',
saving: 'Saving',
activeRuns: 'Active runs',
completed: 'Completed',
queue: 'Queue',
artifactsPreview: 'Project artifacts preview',
nav: { all: 'All projects', running: 'Running', completed: 'Completed', archived: 'Archived' },
tab: { overview: 'Overview', settings: 'Settings', members: 'Members' },
newProject: 'New project',
edit: 'Edit project',
projectRuns: 'Project runs',
loading: 'Loading projects…',
loadMoreError: 'Failed to load more projects: {{message}}',
Expand Down Expand Up @@ -934,6 +1012,16 @@ export const sharedWorkbenchResources = {
},
},
contacts: {
manage: 'Manage',
recentContacts: 'Recent contacts',
tokenDanceGroups: 'TokenDance groups',
openGroupChat: 'Open group chat',
enter: 'Open',
loadMore: 'Load more',
filter: 'Filter',
newTicket: 'New ticket',
add: 'Add contact',
new: { title: 'New contacts', subtitle: 'Search users, send friend requests, and handle incoming requests.' },
error: {
title: 'Failed to load contacts',
description: 'Please retry later; check the Hub connection if this persists.',
Expand Down Expand Up @@ -962,6 +1050,17 @@ export const sharedWorkbenchResources = {
},
},
docs: {
home: 'Home',
create: 'New',
deleteConfirm: 'Confirm delete',
cancel: 'Cancel',
colTitle: 'Title',
colLocation: 'Location',
colOwner: 'Owner',
colOpenedAt: 'Opened',
lightPreview: 'Lightweight document preview',
myLibrary: 'My document library',
loading: 'Loading…',
tab: { recent: 'Recent', mine: 'Mine', shared: 'Shared with me', starred: 'Starred' },
search: { placeholder: 'Search docs' },
newDoc: 'New document',
Expand All @@ -984,6 +1083,7 @@ export const sharedWorkbenchResources = {
},
},
agents: {
allowConfirmDeny: 'Allow / Confirm / Deny',
nav: {
installed: 'Installed',
market: 'Agent marketplace',
Expand Down Expand Up @@ -1274,6 +1374,30 @@ export const sharedWorkbenchResources = {
},
},
tasks: {
navTasks: 'Tasks',
taskCenter: 'Cross-project task center; project pages show only that project\'s tasks and runs.',
quickAccess: 'Quick access',
newGroup: 'New group',
currentSelection: 'Current selection',
statusFilter: 'Progress status',
assignedToMe: 'Assigned to me',
groupByProject: 'Group by project',
viewAssigneeTasks: 'View assignee tasks',
edit: 'Edit',
delete: 'Delete',
save: 'Save',
cancel: 'Cancel',
taskTitle: 'Task title',
project: 'Project',
assignee: 'Assignee',
startAt: 'Start',
dueAt: 'Due',
creator: 'Creator',
importTasks: 'Import tasks',
exportView: 'Export current view',
manageFields: 'Manage task fields',
filter: 'Filter',
loadMore: 'Load more',
nav: { all: 'All tasks', assigned: 'Assigned to me', created: 'Created by me', watching: 'Watching' },
view: { list: 'List', board: 'Board', timeline: 'Timeline' },
newTask: 'New task',
Expand Down
4 changes: 2 additions & 2 deletions app/shared/src/ui/DocxPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const DocxPreview: React.FC<DocxPreviewProps> = ({
{loading && (
<div className={styles.loading}>
<div className={styles.spinner} />
<span>正在解析文档...</span>
<span>{t('preview.parsing')}</span>
</div>
)}

Expand All @@ -129,7 +129,7 @@ export const DocxPreview: React.FC<DocxPreviewProps> = ({
<span className={styles.errorMessage}>{error}</span>
<button className={styles.retryBtn} onClick={loadDocx} type="button">
<RotateCcw size={14} />
<span>重试</span>
<span>{t('preview.retry')}</span>
</button>
</div>
)}
Expand Down
21 changes: 13 additions & 8 deletions app/shared/src/ui/SlideshowPreview.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { useTestI18nLanguage } from '../testing/i18n';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import React from 'react';
Expand All @@ -25,6 +26,10 @@ import React from 'react';
`findByText` would throw on a multiple-match.
═══════════════════════════════════════════════════════════════════════ */

beforeAll(async () => {
await useTestI18nLanguage('zh');
});

const mockSlideContents: Record<string, string> = {
'ppt/slides/slide1.xml':
'<a:t>First Slide Heading Text</a:t><a:t>First body content</a:t>',
Expand Down Expand Up @@ -125,23 +130,23 @@ describe('SlideshowPreview', () => {
const onClose = vi.fn();
const { findByRole } = renderSlideshow({ onClose });
// The shared test i18n instance runs in key-echo mode, so
// t('aria.closePreview') returns 'aria.closePreview'.
const closeButton = await findByRole('button', { name: 'aria.closePreview' });
// Real zh copy for the close-preview control (#2154 UIUX lane i18n pass).
const closeButton = await findByRole('button', { name: '关闭预览' });
expect(closeButton).toBeInTheDocument();
});

it('shows prev/next buttons with key-echo aria-labels', async () => {
const { findByRole } = renderSlideshow();
const prevButton = await findByRole('button', { name: 'aria.previousImage' });
const nextButton = await findByRole('button', { name: 'aria.nextImage' });
const prevButton = await findByRole('button', { name: '上一张' });
const nextButton = await findByRole('button', { name: '下一张' });
expect(prevButton).toBeInTheDocument();
expect(nextButton).toBeInTheDocument();
});

it('calls onClose when close button is clicked', async () => {
const onClose = vi.fn();
const { findByRole } = renderSlideshow({ onClose });
const closeButton = await findByRole('button', { name: 'aria.closePreview' });
const closeButton = await findByRole('button', { name: '关闭预览' });
fireEvent.click(closeButton);
expect(onClose).toHaveBeenCalledTimes(1);
});
Expand All @@ -150,7 +155,7 @@ describe('SlideshowPreview', () => {
const { findByRole, findByText, getByText } = renderSlideshow();
// Wait for slide 1 to be rendered before interacting.
await findByText('First Slide Heading Text');
const nextButton = await findByRole('button', { name: 'aria.nextImage' });
const nextButton = await findByRole('button', { name: '下一张' });
fireEvent.click(nextButton);
// Slide 2 heading now renders in the slide canvas.
expect(await findByText('Second Slide Heading Text')).toBeInTheDocument();
Expand All @@ -159,7 +164,7 @@ describe('SlideshowPreview', () => {

it('disables prev button on the first slide', async () => {
const { findByRole } = renderSlideshow();
const prevButton = await findByRole('button', { name: 'aria.previousImage' });
const prevButton = await findByRole('button', { name: '上一张' });
expect(prevButton).toBeDisabled();
});

Expand Down
6 changes: 3 additions & 3 deletions app/shared/src/ui/SlideshowPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const SlideshowPreview: React.FC<SlideshowPreviewProps> = ({
{loading && (
<div className={styles.loading}>
<div className={styles.spinner} />
<span>正在解析演示文稿...</span>
<span>{t('preview.parsingSlideshow')}</span>
</div>
)}

Expand All @@ -268,7 +268,7 @@ export const SlideshowPreview: React.FC<SlideshowPreviewProps> = ({
<span className={styles.errorMessage}>{error}</span>
<button className={styles.retryBtn} onClick={loadPptx} type="button">
<RotateCcw size={14} />
<span>重试</span>
<span>{t('preview.retry')}</span>
</button>
</div>
)}
Expand Down Expand Up @@ -301,7 +301,7 @@ export const SlideshowPreview: React.FC<SlideshowPreviewProps> = ({
</div>
)}
{currentSlideData.text.length === 0 && currentSlideData.images.length === 0 && (
<div className={styles.slideEmpty}>空白幻灯片</div>
<div className={styles.slideEmpty}>{t('preview.blankSlide')}</div>
)}
</div>
</div>
Expand Down
Loading
Loading