237 lines
7.5 KiB
TypeScript
237 lines
7.5 KiB
TypeScript
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 a gear with 10 teeth and use sensible defaults for everything else...',
|
||
'创建一个 10 齿齿轮,其余参数使用合理默认值...',
|
||
],
|
||
['Design Studio', 'HiCAD'],
|
||
['Design using traditional point-and-click', '使用传统点击方式设计'],
|
||
['Dismiss', '关闭'],
|
||
['Edit', '编辑'],
|
||
['Export', '导出'],
|
||
['Export part', '导出零件'],
|
||
['Extrude', '拉伸'],
|
||
['Feature Tree', '特征树'],
|
||
['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 Files', '项目文件'],
|
||
['Publish', '发布'],
|
||
['Redo', '重做'],
|
||
['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', '缩放至合适大小'],
|
||
['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)
|
||
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-hicad-official-account-link] {
|
||
display: none !important;
|
||
}
|
||
`
|
||
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,
|
||
})
|
||
}
|