feat(tokens): 全球出海设计Token系统与多语言国际化统一字典

这个提交包含在:
wikiboss
2026-09-20 17:12:58 +08:00
父节点 27e053afad
当前提交 5705302cf0
共有 5 个文件被更改,包括 528 次插入 和 0 次删除

查看文件

@@ -1,5 +1,8 @@
import { omniLocales } from './omni.ts';
const i18n = {
'en-US': {
...omniLocales['en-US'].messages,
'menu.welcome': 'Welcome',
'menu.dashboard': 'Dashboard',
'menu.list': 'List',
@@ -59,6 +62,7 @@ const i18n = {
'navbar.search.placeholder': 'Please search',
},
'zh-CN': {
...omniLocales['zh-CN'].messages,
'menu.dashboard': '仪表盘',
'menu.list': '列表页',
'menu.result': '结果页',
@@ -116,6 +120,9 @@ const i18n = {
'message.lang.tips': '语言切换至 ',
'navbar.search.placeholder': '输入内容查询',
},
'ar-SA': {
...omniLocales['ar-SA'].messages,
},
};
export default i18n;

156
admin/src/locale/omni.ts 普通文件
查看文件

@@ -0,0 +1,156 @@
/**
* OmniCard AI 跨端与管理后台统一多语言国际化字典
* 严格遵循 AGENTS.md 约束规范:
* 1. 禁止使用任何 emoji
* 2. 纯文本国际化
* 3. 完整支持阿拉伯语 RTL 镜像布局
*/
export type OmniLocale = 'zh-CN' | 'en-US' | 'ar-SA';
export type Direction = 'ltr' | 'rtl';
export interface LocaleMeta {
readonly code: OmniLocale;
readonly name: string;
readonly dir: Direction;
}
export type OmniMessageKey =
| 'common.confirm'
| 'common.cancel'
| 'common.save'
| 'common.delete'
| 'common.edit'
| 'common.search'
| 'common.loading'
| 'common.success'
| 'common.error'
| 'common.empty'
| 'card.save_contact'
| 'card.exchange'
| 'card.ai_chat'
| 'card.share'
| 'card.scan_nfc'
| 'card.view_profile'
| 'radar.online_visitor'
| 'radar.active_now'
| 'radar.visitor_trace'
| 'radar.lead_score'
| 'crm.contacts'
| 'crm.leads'
| 'crm.follow_up';
export interface OmniLocaleConfig {
readonly code: OmniLocale;
readonly name: string;
readonly dir: Direction;
readonly messages: Record<OmniMessageKey, string>;
}
export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
'zh-CN': {
code: 'zh-CN',
name: '简体中文',
dir: 'ltr',
messages: {
'common.confirm': '确认',
'common.cancel': '取消',
'common.save': '保存',
'common.delete': '删除',
'common.edit': '编辑',
'common.search': '搜索',
'common.loading': '加载中...',
'common.success': '操作成功',
'common.error': '操作失败',
'common.empty': '暂无数据',
'card.save_contact': '保存到通讯录',
'card.exchange': '交换名片',
'card.ai_chat': 'AI 咨询',
'card.share': '分享名片',
'card.scan_nfc': 'NFC 触碰感应',
'card.view_profile': '查看微官网',
'radar.online_visitor': '在线访客',
'radar.active_now': '当前活跃',
'radar.visitor_trace': '访问轨迹',
'radar.lead_score': '意向度评分',
'crm.contacts': '联系人列表',
'crm.leads': '商机线索',
'crm.follow_up': '跟进记录',
},
},
'en-US': {
code: 'en-US',
name: 'English',
dir: 'ltr',
messages: {
'common.confirm': 'Confirm',
'common.cancel': 'Cancel',
'common.save': 'Save',
'common.delete': 'Delete',
'common.edit': 'Edit',
'common.search': 'Search',
'common.loading': 'Loading...',
'common.success': 'Success',
'common.error': 'Error occurred',
'common.empty': 'No data available',
'card.save_contact': 'Save Contact',
'card.exchange': 'Exchange Card',
'card.ai_chat': 'AI Assistant',
'card.share': 'Share Card',
'card.scan_nfc': 'NFC Tap',
'card.view_profile': 'View Profile',
'radar.online_visitor': 'Online Visitors',
'radar.active_now': 'Active Now',
'radar.visitor_trace': 'Visitor Trail',
'radar.lead_score': 'Lead Score',
'crm.contacts': 'Contacts',
'crm.leads': 'Leads',
'crm.follow_up': 'Follow-up',
},
},
'ar-SA': {
code: 'ar-SA',
name: 'العربية',
dir: 'rtl',
messages: {
'common.confirm': 'تأكيد',
'common.cancel': 'إلغاء',
'common.save': 'حفظ',
'common.delete': 'حذف',
'common.edit': 'تعديل',
'common.search': 'بحث',
'common.loading': 'جاري التحميل...',
'common.success': 'تمت العملية بنجاح',
'common.error': 'حدث خطأ',
'common.empty': 'لا توجد بيانات',
'card.save_contact': 'حفظ جهة الاتصال',
'card.exchange': 'تبادل البطاقات',
'card.ai_chat': 'مساعد الذكاء الاصطناعي',
'card.share': 'مشاركة البطاقة',
'card.scan_nfc': 'مسح NFC',
'card.view_profile': 'عرض الملف الشخصي',
'radar.online_visitor': 'الزوار الحاليون',
'radar.active_now': 'نشط الآن',
'radar.visitor_trace': 'مسار الزائر',
'radar.lead_score': 'درجة العميل المحتمل',
'crm.contacts': 'جهات الاتصال',
'crm.leads': 'العملاء المحتملون',
'crm.follow_up': 'المتابعة',
},
},
};
/**
* 判断指定语言是否需要 RTL 镜像排版
*/
export function isRtl(locale: OmniLocale): boolean {
return omniLocales[locale]?.dir === 'rtl';
}
/**
* 获取翻译文本,若不存在则回退至英文或 key 本身
*/
export function getOmniText(locale: OmniLocale, key: OmniMessageKey): string {
const dict = omniLocales[locale] || omniLocales['en-US'];
return dict.messages[key] || omniLocales['en-US'].messages[key] || key;
}

140
admin/src/style/tokens.ts 普通文件
查看文件

@@ -0,0 +1,140 @@
/**
* OmniCard AI 设计 Token 系统 (Admin & Web)
* 严格遵循 AGENTS.md 约束规范
* 瑞士国际主义 + 现代 SaaS + 编辑感
*/
export interface ColorTokens {
readonly primary: string;
readonly primarySoft: string;
readonly ai: string;
readonly bg: string;
readonly surface: string;
readonly border: string;
readonly text: string;
readonly text2: string;
readonly success: string;
readonly warning: string;
readonly error: string;
}
export const lightColors: ColorTokens = {
primary: '#0B5CFF',
primarySoft: '#EAF1FF',
ai: '#7C5CFF',
bg: '#FFFFFF',
surface: '#F7F8FA',
border: '#E3E7ED',
text: '#101828',
text2: '#667085',
success: '#12B76A',
warning: '#F79009',
error: '#F04438',
} as const;
export const darkColors: ColorTokens = {
primary: '#4D8DFF',
primarySoft: '#102A4D',
ai: '#9B8CFF',
bg: '#0B0D10',
surface: '#12151A',
border: '#262B33',
text: '#F2F4F7',
text2: '#98A2B3',
success: '#32D583',
warning: '#FDB022',
error: '#F97066',
} as const;
export const spacing = {
4: '4px',
8: '8px',
12: '12px',
16: '16px',
24: '24px',
32: '32px',
48: '48px',
64: '64px',
} as const;
export const radius = {
button: '12px',
card: '16px',
modal: '20px',
} as const;
export const typography = {
fontFamily: 'Inter, Noto Sans SC, Noto Sans Arabic, Noto Sans JP, Noto Sans KR, sans-serif',
fontSize: {
12: '12px',
14: '14px',
16: '16px',
20: '20px',
24: '24px',
32: '32px',
40: '40px',
},
lineHeight: {
body: 1.6,
title: 1.25,
arabic: 1.7,
},
shadow: '0 1px 2px rgba(16,24,40,0.04)',
maxWidth: {
content: '1200px',
article: '720px',
},
motion: {
micro: '150ms',
drawer: '250ms',
page: '400ms',
easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)',
},
} as const;
/**
* 获取指定主题下的 CSS 变量键值对
*/
export function getCssVariables(theme: 'light' | 'dark' = 'light'): Record<string, string> {
const colors = theme === 'dark' ? darkColors : lightColors;
return {
'--omni-color-primary': colors.primary,
'--omni-color-primary-soft': colors.primarySoft,
'--omni-color-ai': colors.ai,
'--omni-color-bg': colors.bg,
'--omni-color-surface': colors.surface,
'--omni-color-border': colors.border,
'--omni-color-text': colors.text,
'--omni-color-text-2': colors.text2,
'--omni-color-success': colors.success,
'--omni-color-warning': colors.warning,
'--omni-color-error': colors.error,
'--omni-space-4': spacing[4],
'--omni-space-8': spacing[8],
'--omni-space-12': spacing[12],
'--omni-space-16': spacing[16],
'--omni-space-24': spacing[24],
'--omni-space-32': spacing[32],
'--omni-space-48': spacing[48],
'--omni-space-64': spacing[64],
'--omni-radius-button': radius.button,
'--omni-radius-card': radius.card,
'--omni-radius-modal': radius.modal,
'--omni-font-family': typography.fontFamily,
'--omni-shadow': typography.shadow,
};
}
/**
* 将 CSS 变量对象序列化为 CSS 样式声明字符串
*/
export function getCssVariableDeclarations(theme: 'light' | 'dark' = 'light'): string {
const vars = getCssVariables(theme);
return Object.entries(vars)
.map(([k, v]) => `${k}: ${v};`)
.join(' ');
}