223 lines
7.8 KiB
TypeScript
223 lines
7.8 KiB
TypeScript
import { DEFAULT_LOCALE, type Locale } from '@/i18n/config';
|
||
|
||
/**
|
||
* 单文件翻译字典。
|
||
*
|
||
* 设计(按你的要求):
|
||
* - key 就是「英文原文」,直接当文案用,无需额外维护英文副本;
|
||
* - value 只存「非默认语言」的翻译(本项目默认语言是 en,所以只存 cn 等);
|
||
* - 英文环境调用 t(key) 直接返回 key 本身;
|
||
* - 其它语言从 value 里取对应翻译,取不到就回退英文(开发环境会告警,方便发现漏翻)。
|
||
*
|
||
* 新增一条:直接加一行 `'英文原文': { cn: '中文' }` 即可;
|
||
* 当前是英文时根本不需要在字典里登记,写了 t('Nice day!') 就能渲染 "Nice day!"。
|
||
*/
|
||
|
||
// 除默认语言外的所有语言(如 'cn')。加新语言(ja 等)时这里会自动包含。
|
||
type OtherLocale = Exclude<Locale, typeof DEFAULT_LOCALE>;
|
||
type TranslationEntry = Partial<Record<OtherLocale, string>>;
|
||
|
||
export const dict: Record<string, TranslationEntry> = {
|
||
|
||
// ── 导航 / Navigation ──
|
||
|
||
'全部的 runway': { cn: '全部的 runway' },
|
||
|
||
'home': { cn: '首页' },
|
||
|
||
'login': { cn: '登录' },
|
||
|
||
'portfolio': { cn: '作品集' },
|
||
|
||
'runway archive': { cn: '走秀档案' },
|
||
|
||
// ── Runway 子分类(header 下拉,与列表页 COLLECTION 筛选一致)──
|
||
'ready-to-wear': { cn: '女装成衣' },
|
||
'menswear': { cn: '男装' },
|
||
'couture': { cn: '高级定制' },
|
||
'resort': { cn: '度假系列' },
|
||
'pre-fall': { cn: '早秋系列' },
|
||
|
||
'signup': { cn: '注册' },
|
||
|
||
// ── 账号 / Account ──
|
||
'hi': { cn: '你好' },
|
||
'password': { cn: '密码' },
|
||
'account': { cn: '账户' },
|
||
'sign out': { cn: '退出登录' },
|
||
'my account': { cn: '我的账户' },
|
||
'username or email': { cn: '用户名或邮箱' },
|
||
'login failed': { cn: '登录失败,请检查账号或密码' },
|
||
'please log in': { cn: '请先登录' },
|
||
'login to continue': { cn: '登录以继续使用筛选与翻页' },
|
||
'login prompt': { cn: '登录后可筛选与翻页浏览全部档案' },
|
||
'members': { cn: '会员专区' },
|
||
'back to site': { cn: '返回网站' },
|
||
'email': { cn: '邮箱' },
|
||
'my collections': { cn: '我的收藏' },
|
||
'my history': { cn: '浏览历史' },
|
||
'coming soon': { cn: '敬请期待' },
|
||
|
||
// ── 账户中心 / Account Center(企业级个人中心)──
|
||
'overview': { cn: '概览' },
|
||
'sessions & security': { cn: '会话与安全' },
|
||
'settings': { cn: '设置' },
|
||
'saved looks': { cn: '收藏造型' },
|
||
'active sessions': { cn: '活跃会话' },
|
||
'member since': { cn: '加入于' },
|
||
'role': { cn: '角色' },
|
||
'internal member': { cn: '内部成员' },
|
||
'items': { cn: '条' },
|
||
'account overview': { cn: '账户概览' },
|
||
'account details': { cn: '账户详情' },
|
||
'username': { cn: '用户名' },
|
||
'edit profile': { cn: '编辑资料' },
|
||
'view archive': { cn: '浏览档案' },
|
||
'no collections yet': { cn: '暂无收藏内容' },
|
||
'no history yet': { cn: '暂无浏览记录' },
|
||
'browse the archive': { cn: '去浏览档案' },
|
||
'current session': { cn: '当前会话' },
|
||
'this device': { cn: '此设备' },
|
||
'last active': { cn: '最近活跃' },
|
||
'sign out all devices': { cn: '退出所有设备' },
|
||
'single session note': { cn: '为保障账号安全,同一账号仅允许在一处登录,新登录会顶替旧会话。' },
|
||
'preferences': { cn: '偏好设置' },
|
||
'appearance': { cn: '外观' },
|
||
'notifications': { cn: '通知' },
|
||
'soon': { cn: '即将推出' },
|
||
'quick stats': { cn: '数据概览' },
|
||
'now': { cn: '刚刚' },
|
||
'member profile': { cn: '会员资料' },
|
||
'save': { cn: '收藏' },
|
||
'saved': { cn: '已收藏' },
|
||
'remove': { cn: '移除' },
|
||
'cancel': { cn: '取消' },
|
||
'save changes': { cn: '保存修改' },
|
||
'display name': { cn: '昵称' },
|
||
'bio': { cn: '简介' },
|
||
'clear history': { cn: '清空历史' },
|
||
'no saved looks yet': { cn: '暂无收藏造型' },
|
||
'no history yet': { cn: '暂无浏览记录' },
|
||
'all': { cn: '全部' },
|
||
'image': { cn: '图片' },
|
||
'gallery': { cn: '图集' },
|
||
'viewed gallery': { cn: '浏览的图集' },
|
||
'light': { cn: '浅色' },
|
||
'dark': { cn: '深色' },
|
||
'on': { cn: '开' },
|
||
'off': { cn: '关' },
|
||
|
||
// ── 控件 / Controls ──
|
||
|
||
'back to index': { cn: '返回首页' },
|
||
|
||
'back to top': { cn: '回到顶部' },
|
||
|
||
'close': { cn: '关闭' },
|
||
|
||
'previous image': { cn: '上一张' },
|
||
|
||
'next image': { cn: '下一张' },
|
||
|
||
'previous': { cn: '上一组' },
|
||
|
||
'next': { cn: '下一组' },
|
||
|
||
// ── 错误 / Errors ──
|
||
|
||
'no article id specified': { cn: '未指定文章 ID' },
|
||
|
||
'failed to load article. please refresh or try later.': { cn: '加载文章失败,请刷新或稍后再试' },
|
||
|
||
'click image to zoom · scroll to view · esc to close': { cn: '点击图片放大 · 上下滚动查看 · Esc 关闭' },
|
||
|
||
// ── 页脚 / Footer ──
|
||
|
||
'© 2026 portfolio studio. all rights reserved.': { cn: '© 2026 PORTFOLIO STUDIO. 保留所有权利。' },
|
||
|
||
// ── 页脚工具 / Footer tools ──
|
||
|
||
'color studio tools': { cn: '色彩工作室工具' },
|
||
|
||
'ai color trend generator': { cn: 'AI 色彩趋势生成器' },
|
||
|
||
'photo swatch extractor': { cn: '图片色板提取器' },
|
||
|
||
'upload': { cn: '上传' },
|
||
|
||
'reset b&w theme': { cn: '重置黑白主题' },
|
||
|
||
'default': { cn: '默认' },
|
||
|
||
// ── 页脚简介 / Manifesto ──
|
||
|
||
'minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. integrated with 2026 color forecast intelligence and ai palette generator.':
|
||
{ cn: '极简编辑风格作品集,记录高级定制秀场、后台试装与街头文化。集成 2026 色彩预测情报与 AI 调色板生成器。' },
|
||
|
||
// ── 登录态 / Auth ──
|
||
|
||
'hi': { cn: '你好' },
|
||
|
||
'logout': { cn: '退出' },
|
||
|
||
'language': { cn: '语言' },
|
||
|
||
'runway looks': { cn: '秀场造型' },
|
||
|
||
// ── 街拍 / Street Snaps ──
|
||
|
||
'street style': { cn: '街拍' },
|
||
|
||
'street snaps': { cn: '街拍' },
|
||
|
||
'all street snaps': { cn: '全部街拍' },
|
||
|
||
'no street snap id specified': { cn: '未指定街拍 ID' },
|
||
|
||
'failed to load street snap. please refresh or try later.': { cn: '加载街拍失败,请刷新或稍后再试' },
|
||
|
||
// ── 地区 / Cities(街拍按城市分类,值对齐后端 street_snap.city 列;translate 会小写化 key)──
|
||
'city': { cn: '地区' },
|
||
'paris': { cn: '巴黎' },
|
||
'new york': { cn: '纽约' },
|
||
'milan': { cn: '米兰' },
|
||
'london': { cn: '伦敦' },
|
||
'copenhagen': { cn: '哥本哈根' },
|
||
'florence': { cn: '佛罗伦萨' },
|
||
'berlin': { cn: '柏林' },
|
||
'tokyo': { cn: '东京' },
|
||
'seoul': { cn: '首尔' },
|
||
'shanghai': { cn: '上海' },
|
||
'beijing': { cn: '北京' },
|
||
'rome': { cn: '罗马' },
|
||
'madrid': { cn: '马德里' },
|
||
'amsterdam': { cn: '阿姆斯特丹' },
|
||
'antwerp': { cn: '安特卫普' },
|
||
'los angeles': { cn: '洛杉矶' },
|
||
'moscow': { cn: '莫斯科' },
|
||
'mumbai': { cn: '孟买' },
|
||
};
|
||
|
||
/** 把 {name} 形式的占位符替换成参数值。 */
|
||
function interpolate(text: string, params?: Record<string, string | number>): string {
|
||
if (!params) return text;
|
||
return text.replace(/\{(\w+)\}/g, (_, k) => (k in params ? String(params[k]) : `{${k}}`));
|
||
}
|
||
|
||
/**
|
||
* 取翻译:
|
||
* - 当前是默认语言(en)→ 直接返回 key(英文原文);
|
||
* - 其它语言 → 优先取字典里的翻译,缺失则回退英文并返回 key(不会白屏)。
|
||
*/
|
||
export function translate(key: string, locale: Locale, params?: Record<string, string | number>): string {
|
||
if (locale === DEFAULT_LOCALE) return interpolate(key, params);
|
||
|
||
const localized = dict[key.toLowerCase()]?.[locale as OtherLocale];
|
||
if (localized) return interpolate(localized, params);
|
||
|
||
if (import.meta.env.DEV) {
|
||
console.warn(`[i18n] 缺少 "${locale}" 翻译:"${key}",已回退英文`);
|
||
}
|
||
return interpolate(key, params);
|
||
}
|