296 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const translations = new Map<string, string>([
['3D View', '3D 视图'],
['Add file to project', '添加项目文件'],
['All', '全部'],
['Angularity', '倾斜度'],
['Ask me anything', '有问题尽管问我'],
['Attach files', '添加附件'],
['Auto', '自动'],
['Back to HiCAD', '返回 HiCAD'],
[
'Beyond geometry creation, I can help you with model-derived properties like center of mass, volume, and surface area.',
'除了创建几何体,我还可以分析质心、体积和表面积等模型属性。',
],
['Bodies', '实体'],
['Chamfer', '倒角'],
['Choose your adventure', '选择一种建模方式'],
['Circular Pattern', '圆周阵列'],
['Capture viewport screenshot', '截取视口'],
['Code', '代码'],
['Code Editor', '代码编辑器'],
['Code-driven CAD', '代码驱动 CAD'],
['Commands', '命令'],
['Connected', '已连接'],
['Connecting and setting up scene...', '正在连接并初始化场景...'],
['Create a new project', '新建项目'],
['Create project', '新建项目'],
['Create from a sample', '从示例开始'],
[
'Create a gear with 10 teeth and use sensible defaults for everything else...',
'创建一个 10 齿齿轮,其余参数使用合理默认值...',
],
['Design Studio', 'HiCAD'],
['Design using traditional point-and-click', '使用传统点击方式设计'],
['Dismiss', '关闭'],
['Edit', '编辑'],
['Edited', '最近编辑'],
['Export', '导出'],
['Export part', '导出零件'],
['Extrude', '拉伸'],
['Feature Tree', '特征树'],
['Fit all model', '显示全部模型'],
['Fillet', '圆角'],
['Flip Surface', '翻转曲面'],
['Front plane', '前视基准面'],
[
'From designing parts to giving manufacturing feedback, I’m ready to help.',
'从零件设计到制造反馈,我都可以协助你。',
],
['Helix', '螺旋线'],
['Hello there, I\u2019m Zookeeper.', '你好,我是 HiCAD AI 助手。'],
["Hello there, I'm Zookeeper.", '你好,我是 HiCAD AI 助手。'],
['Help', '帮助'],
['Help and resources', '帮助与资源'],
['Hole', '孔'],
['How would you like to start?', '想从哪里开始?'],
[
"I'm here to help you create and edit real, parametric CAD geometry through incremental, structured commands.",
'我可以通过渐进、结构化的命令,帮助你创建和编辑参数化 CAD 几何体。',
],
[
'I create structured, fully editable geometry by building features step by step.',
'我会逐步构建特征,生成结构化且可完整编辑的几何模型。',
],
['Insert', '插入'],
['Logs', '日志'],
['Loft', '放样'],
['Measure', '测量'],
['Mirror', '镜像'],
['No selection', '未选择'],
['No selection.', '未选择。'],
['Offset Plane', '偏移基准面'],
['Open project', '打开项目'],
['Project Libraries', '我的 CAD 项目'],
['Projects', '项目'],
['Project Files', '项目文件'],
['Publish', '发布'],
['Redo', '重做'],
['Rename project', '重命名'],
['Delete project', '删除项目'],
['Search projects', '搜索项目'],
['Sort by', '排序'],
['Name', '名称'],
['Last Modified', '最近编辑'],
['No projects found', '还没有项目'],
[', ready to make your first one?', ',点击“新建项目”开始设计。'],
[
'No projects found, ready to make your first one?',
'还没有项目,点击“新建项目”开始设计。',
],
['Submit', '确认'],
['Continue', '下一步'],
['Step back', '返回上一步'],
['Enter a value', '请输入'],
['Cancel', '取消'],
['Revolve', '旋转'],
['Settings', '设置'],
['Shell', '抽壳'],
['Side plane', '侧视基准面'],
['Sketch', '草图'],
['Split', '分割'],
['Start Sketch', '新建草图'],
['Supplemental analysis', '补充分析'],
['Sweep', '扫掠'],
['Top plane', '顶视基准面'],
['Translate', '平移'],
['Turn thoughts into geometry', '将想法变成几何模型'],
['Undo', '撤销'],
['Union', '并集'],
[
'Upload an image and I’ll interpret it, extract intent, and help you design or improve the geometry step by step.',
'上传图片后,我会理解参考内容、提取设计意图,并逐步帮助你设计或改进几何模型。',
],
[
'Upload your image to work from real references',
'上传图片并基于真实参考进行设计',
],
['Updating feature tree...', '正在更新特征树...'],
['Variables', '变量'],
['View', '视图'],
['View settings', '视图设置'],
['Zookeeper', 'HiCAD AI 助手'],
['Zookeeper, our conversational CAD agent', 'HiCAD 对话式 CAD 助手'],
[
'Zookeeper can make mistakes. We send selection context to help. Always verify information.',
'HiCAD AI 可能出错。我们会发送选区上下文以提高准确性,请始终核对结果。',
],
['Zoom to fit', '缩放至合适大小'],
['Reset view', '重置视图'],
['loading', '加载中'],
['zoo.dev', 'HiCAD 云端'],
])
const translatableAttributes = ['aria-label', 'placeholder', 'title'] as const
const excludedSelector = [
'code',
'pre',
'script',
'style',
'textarea',
'input',
'[contenteditable="true"]',
'.cm-editor',
'.cm-content',
'.cm-line',
].join(',')
const excludedAttributeSelector = [
'script',
'style',
'.cm-editor',
'.cm-content',
'.cm-line',
].join(',')
function normalizeLookupKey(value: string) {
return value.replace(/\s+/g, ' ').trim()
}
function isExcluded(node: Node) {
const element =
node.nodeType === Node.ELEMENT_NODE
? (node as Element)
: node.parentElement
return Boolean(element?.closest(excludedSelector))
}
function translateTextNode(node: Text) {
if (isExcluded(node)) return
const value = node.nodeValue || ''
const normalized = normalizeLookupKey(value)
const translated = translations.get(normalized)
if (!translated || translated === normalized) return
const leadingWhitespace = value.match(/^\s*/)?.[0] || ''
const trailingWhitespace = value.match(/\s*$/)?.[0] || ''
node.nodeValue = `${leadingWhitespace}${translated}${trailingWhitespace}`
}
function translateElement(element: Element) {
if (element.closest(excludedAttributeSelector)) return
for (const attribute of translatableAttributes) {
const value = normalizeLookupKey(element.getAttribute(attribute) || '')
if (!value) continue
const translated =
translations.get(value) ||
(attribute === 'placeholder' && value.startsWith('Search projects')
? '搜索项目'
: undefined)
if (translated) element.setAttribute(attribute, translated)
}
}
function translateTree(root: Node) {
if (root.nodeType === Node.TEXT_NODE) {
translateTextNode(root as Text)
return
}
if (root.nodeType === Node.ELEMENT_NODE) {
translateElement(root as Element)
}
const walker = document.createTreeWalker(
root,
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
)
let current = walker.nextNode()
while (current) {
if (current.nodeType === Node.TEXT_NODE) {
translateTextNode(current as Text)
} else {
translateElement(current as Element)
}
current = walker.nextNode()
}
}
export function installHiCadLocalization() {
document.documentElement.lang = 'zh-CN'
document.documentElement.dataset.hicadEmbedded = 'true'
const style = document.createElement('style')
style.textContent = `
html[data-hicad-embedded='true'] body {
font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
}
html[data-hicad-embedded='true'] [data-testid='download-desktop-app'],
html[data-hicad-embedded='true'] [data-testid='billing-remaining-bar'],
html[data-hicad-embedded='true'] [data-testid='onboarding-toast'],
html[data-hicad-embedded='true'] [data-hicad-official-account-link] {
display: none !important;
}
html[data-hicad-embedded='true'] [data-testid='home-sidebar'],
html[data-hicad-embedded='true'] [data-testid='home-new-file'],
html[data-hicad-embedded='true'] [data-testid='environment-subdomain-bar'],
html[data-hicad-embedded='true'] [data-testid='cloud-sync-status'] {
display: none !important;
}
html[data-hicad-embedded='true'] .home-layout {
display: block !important;
column-gap: 2rem;
margin-top: clamp(1.5rem, 5vh, 4rem);
}
html[data-hicad-embedded='true'] [data-testid='home-header'] > p:has(
[data-testid='project-directory-settings-link']
) {
display: none !important;
}
html[data-hicad-embedded='true'] [data-testid='home-section'] {
margin-top: 1.5rem;
overflow-y: auto;
padding-bottom: 3rem;
}
html[data-hicad-embedded='true'] [data-testid='home-section'] > ul {
align-content: start;
}
html[data-hicad-embedded='true'] [data-testid='project-status-badge'] {
box-shadow: 0 1px 2px rgb(0 0 0 / 8%);
}
html[data-hicad-embedded='true'] [data-testid='project-directory-settings-link'] {
overflow-wrap: anywhere;
}
`
document.head.appendChild(style)
translateTree(document.documentElement)
let frame = 0
const pendingRoots = new Set<Node>()
const flush = () => {
frame = 0
for (const root of pendingRoots) translateTree(root)
pendingRoots.clear()
}
const schedule = (root: Node) => {
pendingRoots.add(root)
if (!frame) frame = window.requestAnimationFrame(flush)
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) schedule(node)
} else {
schedule(mutation.target)
}
}
})
observer.observe(document.documentElement, {
attributeFilter: [...translatableAttributes],
attributes: true,
characterData: true,
childList: true,
subtree: true,
})
}