183 lines
5.3 KiB
TypeScript
183 lines
5.3 KiB
TypeScript
const translations = new Map<string, string>([
|
|
['3D View', '3D 视图'],
|
|
['Add file to project', '添加项目文件'],
|
|
['All', '全部'],
|
|
['Angularity', '倾斜度'],
|
|
['Ask me anything', '有问题尽管问我'],
|
|
['Back to HiCAD', '返回 HiCAD'],
|
|
['Bodies', '实体'],
|
|
['Chamfer', '倒角'],
|
|
['Choose your adventure', '选择一种建模方式'],
|
|
['Circular Pattern', '圆周阵列'],
|
|
['Code', '代码'],
|
|
['Code Editor', '代码编辑器'],
|
|
['Code-driven CAD', '代码驱动 CAD'],
|
|
['Commands', '命令'],
|
|
['Connected', '已连接'],
|
|
['Connecting and setting up scene...', '正在连接并初始化场景...'],
|
|
['Create a new project', '新建项目'],
|
|
['Design using traditional point-and-click', '使用传统点击方式设计'],
|
|
['Dismiss', '关闭'],
|
|
['Edit', '编辑'],
|
|
['Export', '导出'],
|
|
['Export part', '导出零件'],
|
|
['Extrude', '拉伸'],
|
|
['Feature Tree', '特征树'],
|
|
['Fillet', '圆角'],
|
|
['Flip Surface', '翻转曲面'],
|
|
['Front plane', '前视基准面'],
|
|
['Helix', '螺旋线'],
|
|
['Hello there, I\u2019m Zookeeper.', '你好,我是 HiCAD AI 助手。'],
|
|
["Hello there, I'm Zookeeper.", '你好,我是 HiCAD AI 助手。'],
|
|
['Help', '帮助'],
|
|
['Hole', '孔'],
|
|
['How would you like to start?', '想从哪里开始?'],
|
|
['Insert', '插入'],
|
|
['Logs', '日志'],
|
|
['Loft', '放样'],
|
|
['Mirror', '镜像'],
|
|
['No selection', '未选择'],
|
|
['No selection.', '未选择。'],
|
|
['Offset Plane', '偏移基准面'],
|
|
['Open project', '打开项目'],
|
|
['Project Files', '项目文件'],
|
|
['Publish', '发布'],
|
|
['Redo', '重做'],
|
|
['Revolve', '旋转'],
|
|
['Settings', '设置'],
|
|
['Shell', '抽壳'],
|
|
['Side plane', '侧视基准面'],
|
|
['Sketch', '草图'],
|
|
['Split', '分割'],
|
|
['Start Sketch', '新建草图'],
|
|
['Sweep', '扫掠'],
|
|
['Top plane', '顶视基准面'],
|
|
['Translate', '平移'],
|
|
['Undo', '撤销'],
|
|
['Union', '并集'],
|
|
['Variables', '变量'],
|
|
['View', '视图'],
|
|
['View settings', '视图设置'],
|
|
['Zookeeper', 'HiCAD AI 助手'],
|
|
['Zookeeper, our conversational CAD agent', 'HiCAD 对话式 CAD 助手'],
|
|
['Zoom to fit', '缩放至合适大小'],
|
|
])
|
|
|
|
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(',')
|
|
|
|
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 = value.trim()
|
|
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 (isExcluded(element)) return
|
|
for (const attribute of translatableAttributes) {
|
|
const value = element.getAttribute(attribute)?.trim()
|
|
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,
|
|
})
|
|
}
|