@file-viewer/renderer-eda 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/eda.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { FileRenderContext, FileViewerRenderedInstance } from '@file-viewer/core';
1
+ import { type FileRenderContext, type FileViewerRenderedInstance } from '@file-viewer/core';
2
2
  export default function renderEda(buffer: ArrayBuffer, target: HTMLDivElement, type?: string, context?: FileRenderContext): Promise<FileViewerRenderedInstance>;
package/dist/eda.js CHANGED
@@ -1,22 +1,131 @@
1
+ import { resolveFileViewerLocale, } from '@file-viewer/core';
1
2
  import { createEdaLayoutWebglBatch, } from '@file-viewer/eda-layout';
2
3
  import { parseEdaFile, } from './edaParser';
3
- const roleLabels = {
4
- root: '根',
5
- library: '',
6
- symbol: '元件符号',
7
- footprint: '封装',
8
- padstack: 'Padstack',
9
- drawing: '图纸',
10
- metadata: '元数据',
11
- property: '属性',
12
- geometry: '几何',
13
- net: '网络',
14
- unknown: '未知',
4
+ const EDA_UI_TEXT = {
5
+ 'zh-CN': {
6
+ 'role.root': '',
7
+ 'role.library': '',
8
+ 'role.symbol': '元件符号',
9
+ 'role.footprint': '封装',
10
+ 'role.padstack': 'Padstack',
11
+ 'role.drawing': '图纸',
12
+ 'role.metadata': '元数据',
13
+ 'role.property': '属性',
14
+ 'role.geometry': '几何',
15
+ 'role.net': '网络',
16
+ 'role.unknown': '未知',
17
+ 'confidence.high': '高',
18
+ 'confidence.medium': '中',
19
+ 'confidence.low': '低',
20
+ 'kind.storage': '目录',
21
+ 'kind.text': '文本',
22
+ 'kind.binary': '二进制',
23
+ 'panel.layout': '版图预览',
24
+ 'empty.noGeometry.title': '没有可绘制几何',
25
+ 'empty.noGeometry.description': '已读取 {layout} 头部和 structure 信息,但未发现 boundary、path、text 或 reference 元素。',
26
+ 'stats.textStreams': '文本流',
27
+ 'stats.binaryStreams': '二进制流',
28
+ 'stats.storageEntries': '目录',
29
+ 'stats.properties': '属性',
30
+ 'stats.symbols': '符号',
31
+ 'stats.footprints': '封装',
32
+ 'stats.confidence': '可信度',
33
+ 'group.symbol': '元件符号',
34
+ 'group.footprint': '封装图形',
35
+ 'group.drawing': '图纸信息',
36
+ 'state.loading': '正在解析 {type}...',
37
+ 'state.errorTitle': 'EDA 预览提示',
38
+ 'header.format': '格式',
39
+ 'header.size': '大小',
40
+ 'header.entries': '条目',
41
+ 'summary.layoutReady': '{layout} 属于芯片版图工程文件。预览器已在浏览器端解析可识别几何,小图生成 SVG,大图自动切换 WebGL canvas,同时保留结构、字符串和诊断索引。',
42
+ 'summary.layoutSafe': 'GDSII / OASIS 属于芯片版图工程文件。预览器优先索引结构、属性、可读字符串和二进制线索,并在纯前端安全退化。',
43
+ 'summary.orcadSafe': 'OLB / DRA 属于 OrCAD / Allegro 生态的私有设计数据。预览器优先解析 CFB 结构、对象候选、属性和可读文本,并在纯前端安全退化。',
44
+ 'search.placeholder': '筛选路径、角色、属性或文本',
45
+ 'selection.title': '当前条目',
46
+ 'selection.none': '未选择',
47
+ 'selection.storageTitle': '目录条目',
48
+ 'selection.storageDescription': '该节点用于组织下级流,没有可直接展示的文本或十六进制片段。',
49
+ 'selection.localStrings': '当前条目字符串',
50
+ 'panel.overview': '解析概览',
51
+ 'panel.tree': '结构树',
52
+ 'panel.objects': 'EDA 对象',
53
+ 'panel.strings': '可读字符串',
54
+ 'panel.diagnostics': '诊断',
55
+ 'panel.nodeCount': '{count} 节点',
56
+ 'panel.itemCount': '{count} 项',
57
+ 'panel.entryCount': '{count} 条目',
58
+ 'empty.noObjects.title': '没有明确对象候选',
59
+ 'empty.noObjects.description': '仍可从结构树、属性和字符串索引中查看可读内容。',
60
+ 'meta.renderer': '渲染器',
61
+ 'meta.library': '库',
62
+ 'meta.userUnit': '用户单位',
63
+ 'meta.dbUnit': '数据库单位',
64
+ },
65
+ 'en-US': {
66
+ 'role.root': 'Root',
67
+ 'role.library': 'Library',
68
+ 'role.symbol': 'Symbol',
69
+ 'role.footprint': 'Footprint',
70
+ 'role.padstack': 'Padstack',
71
+ 'role.drawing': 'Drawing',
72
+ 'role.metadata': 'Metadata',
73
+ 'role.property': 'Property',
74
+ 'role.geometry': 'Geometry',
75
+ 'role.net': 'Net',
76
+ 'role.unknown': 'Unknown',
77
+ 'confidence.high': 'High',
78
+ 'confidence.medium': 'Medium',
79
+ 'confidence.low': 'Low',
80
+ 'kind.storage': 'Directory',
81
+ 'kind.text': 'Text',
82
+ 'kind.binary': 'Binary',
83
+ 'panel.layout': 'Layout preview',
84
+ 'empty.noGeometry.title': 'No drawable geometry',
85
+ 'empty.noGeometry.description': '{layout} headers and structure metadata were read, but no boundary, path, text, or reference elements were found.',
86
+ 'stats.textStreams': 'Text streams',
87
+ 'stats.binaryStreams': 'Binary streams',
88
+ 'stats.storageEntries': 'Directories',
89
+ 'stats.properties': 'Properties',
90
+ 'stats.symbols': 'Symbols',
91
+ 'stats.footprints': 'Footprints',
92
+ 'stats.confidence': 'Confidence',
93
+ 'group.symbol': 'Component symbols',
94
+ 'group.footprint': 'Footprint graphics',
95
+ 'group.drawing': 'Drawing information',
96
+ 'state.loading': 'Parsing {type}...',
97
+ 'state.errorTitle': 'EDA preview notice',
98
+ 'header.format': 'Format',
99
+ 'header.size': 'Size',
100
+ 'header.entries': 'Entries',
101
+ 'summary.layoutReady': '{layout} is an IC layout engineering file. Recognizable geometry is parsed in the browser, small drawings use SVG, large drawings switch to WebGL canvas, and structure, strings, and diagnostics remain indexed.',
102
+ 'summary.layoutSafe': 'GDSII / OASIS are IC layout engineering files. The viewer prioritizes structure, properties, readable strings, and binary clues, then degrades safely in the frontend.',
103
+ 'summary.orcadSafe': 'OLB / DRA are private OrCAD / Allegro design assets. The viewer prioritizes CFB structure, object candidates, properties, and readable text with safe frontend degradation.',
104
+ 'search.placeholder': 'Filter paths, roles, properties, or text',
105
+ 'selection.title': 'Current entry',
106
+ 'selection.none': 'Not selected',
107
+ 'selection.storageTitle': 'Directory entry',
108
+ 'selection.storageDescription': 'This node organizes child streams and has no directly displayable text or hex fragment.',
109
+ 'selection.localStrings': 'Current entry strings',
110
+ 'panel.overview': 'Parse overview',
111
+ 'panel.tree': 'Structure tree',
112
+ 'panel.objects': 'EDA objects',
113
+ 'panel.strings': 'Readable strings',
114
+ 'panel.diagnostics': 'Diagnostics',
115
+ 'panel.nodeCount': '{count} nodes',
116
+ 'panel.itemCount': '{count} items',
117
+ 'panel.entryCount': '{count} entries',
118
+ 'empty.noObjects.title': 'No clear object candidates',
119
+ 'empty.noObjects.description': 'Readable content is still available from the structure tree, properties, and string index.',
120
+ 'meta.renderer': 'Renderer',
121
+ 'meta.library': 'Library',
122
+ 'meta.userUnit': 'User unit',
123
+ 'meta.dbUnit': 'DB unit',
124
+ },
15
125
  };
16
- const confidenceLabels = {
17
- high: '',
18
- medium: '',
19
- low: '低',
126
+ const formatEdaUiText = (locale, key, params = {}) => {
127
+ const template = EDA_UI_TEXT[locale][key] || EDA_UI_TEXT['zh-CN'][key] || key;
128
+ return Object.entries(params).reduce((message, [name, value]) => message.replace(new RegExp(`\\{${name}\\}`, 'g'), String(value)), template);
20
129
  };
21
130
  const edaStyle = `
22
131
  .eda-viewer{position:relative;height:100%;min-height:0;display:flex;flex-direction:column;background:#edf1f5;color:#172033;box-sizing:border-box}
@@ -122,9 +231,12 @@ const formatBytes = (value) => {
122
231
  }
123
232
  return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
124
233
  };
125
- const roleLabel = (role) => roleLabels[role] || role;
126
- const kindLabel = (kind) => {
127
- return kind === 'storage' ? '目录' : kind === 'text' ? '文本' : '二进制';
234
+ const roleLabel = (role, locale) => {
235
+ return formatEdaUiText(locale, `role.${role}`) || role;
236
+ };
237
+ const confidenceLabel = (confidence, locale) => formatEdaUiText(locale, `confidence.${confidence}`);
238
+ const kindLabel = (kind, locale) => {
239
+ return formatEdaUiText(locale, `kind.${kind}`);
128
240
  };
129
241
  const normalizePath = (value) => value.replace(/^\/+/, '').toLowerCase();
130
242
  const flattenTree = (nodes, depth = 0) => {
@@ -328,19 +440,19 @@ const createWebglLayoutPreview = (layout, width, height) => {
328
440
  batch,
329
441
  };
330
442
  };
331
- const createLayoutPreview = (layout) => {
443
+ const createLayoutPreview = (layout, locale) => {
332
444
  const panel = createElement('section', 'eda-panel eda-layout-panel');
333
445
  const layoutLabel = layout.format === 'oasis' ? 'OASIS' : 'GDSII';
334
- appendPanelHead(panel, '版图预览', `${layoutLabel} · ${layout.structureCount || layout.structures.length} structures · ${layout.elements.length} elements`);
446
+ appendPanelHead(panel, formatEdaUiText(locale, 'panel.layout'), `${layoutLabel} · ${layout.structureCount || layout.structures.length} structures · ${layout.elements.length} elements`);
335
447
  const meta = createElement('div', 'eda-layout-meta');
336
448
  [
337
- `Library: ${layout.libraryName || '-'}`,
338
- `User unit: ${formatOptionalNumber(layout.userUnit)}`,
339
- `DB unit: ${formatOptionalNumber(layout.databaseUnit)}`,
449
+ `${formatEdaUiText(locale, 'meta.library')}: ${layout.libraryName || '-'}`,
450
+ `${formatEdaUiText(locale, 'meta.userUnit')}: ${formatOptionalNumber(layout.userUnit)}`,
451
+ `${formatEdaUiText(locale, 'meta.dbUnit')}: ${formatOptionalNumber(layout.databaseUnit)}`,
340
452
  ].forEach(item => meta.append(createElement('span', undefined, item)));
341
453
  panel.append(meta);
342
454
  if (!layout.bounds || !layout.elements.length) {
343
- panel.append(createEmpty('没有可绘制几何', `已读取 ${layoutLabel} 头部和 structure 信息,但未发现 boundary、path、text 或 reference 元素。`));
455
+ panel.append(createEmpty(formatEdaUiText(locale, 'empty.noGeometry.title'), formatEdaUiText(locale, 'empty.noGeometry.description', { layout: layoutLabel })));
344
456
  return panel;
345
457
  }
346
458
  const bounds = layout.bounds;
@@ -367,7 +479,7 @@ const createLayoutPreview = (layout) => {
367
479
  ? createWebglLayoutPreview(layout, svgWidth, svgHeight)
368
480
  : null;
369
481
  if (webglPreview) {
370
- meta.append(createElement('span', undefined, `Renderer: WebGL · ${webglPreview.batch.elementCount} elements`));
482
+ meta.append(createElement('span', undefined, `${formatEdaUiText(locale, 'meta.renderer')}: WebGL · ${webglPreview.batch.elementCount} elements`));
371
483
  webglPreview.batch.warnings.forEach(item => {
372
484
  const warning = createElement('div', 'eda-warning');
373
485
  warning.append(createElement('p', undefined, item));
@@ -377,7 +489,7 @@ const createLayoutPreview = (layout) => {
377
489
  panel.append(canvas);
378
490
  return panel;
379
491
  }
380
- meta.append(createElement('span', undefined, 'Renderer: SVG'));
492
+ meta.append(createElement('span', undefined, `${formatEdaUiText(locale, 'meta.renderer')}: SVG`));
381
493
  const svg = createSvgElement('svg', {
382
494
  class: 'eda-layout-svg',
383
495
  width: svgWidth,
@@ -439,25 +551,25 @@ const createLayoutPreview = (layout) => {
439
551
  panel.append(canvas);
440
552
  return panel;
441
553
  };
442
- const buildStatsCards = (parsed) => {
554
+ const buildStatsCards = (parsed, locale) => {
443
555
  const stats = parsed.stats;
444
556
  return [
445
- { label: '文本流', value: stats.textStreams },
446
- { label: '二进制流', value: stats.binaryStreams },
447
- { label: '目录', value: stats.storageEntries },
448
- { label: '属性', value: stats.propertyCount },
449
- { label: '符号', value: stats.symbolCount },
450
- { label: '封装', value: stats.footprintCount },
557
+ { label: formatEdaUiText(locale, 'stats.textStreams'), value: stats.textStreams },
558
+ { label: formatEdaUiText(locale, 'stats.binaryStreams'), value: stats.binaryStreams },
559
+ { label: formatEdaUiText(locale, 'stats.storageEntries'), value: stats.storageEntries },
560
+ { label: formatEdaUiText(locale, 'stats.properties'), value: stats.propertyCount },
561
+ { label: formatEdaUiText(locale, 'stats.symbols'), value: stats.symbolCount },
562
+ { label: formatEdaUiText(locale, 'stats.footprints'), value: stats.footprintCount },
451
563
  { label: 'Padstack', value: stats.padstackCount },
452
- { label: '可信度', value: confidenceLabels[stats.confidence] },
564
+ { label: formatEdaUiText(locale, 'stats.confidence'), value: confidenceLabel(stats.confidence, locale) },
453
565
  ];
454
566
  };
455
- const buildEntityGroups = (entities) => {
567
+ const buildEntityGroups = (entities, locale) => {
456
568
  const groups = [
457
- { role: 'symbol', label: '元件符号', items: [] },
458
- { role: 'footprint', label: '封装图形', items: [] },
569
+ { role: 'symbol', label: formatEdaUiText(locale, 'group.symbol'), items: [] },
570
+ { role: 'footprint', label: formatEdaUiText(locale, 'group.footprint'), items: [] },
459
571
  { role: 'padstack', label: 'Padstack', items: [] },
460
- { role: 'drawing', label: '图纸信息', items: [] },
572
+ { role: 'drawing', label: formatEdaUiText(locale, 'group.drawing'), items: [] },
461
573
  ];
462
574
  groups.forEach(group => {
463
575
  group.items = entities.filter(entity => entity.role === group.role);
@@ -480,6 +592,7 @@ const createEmpty = (title, description) => {
480
592
  };
481
593
  export default async function renderEda(buffer, target, type = 'olb', context) {
482
594
  const normalizedType = ['dra', 'gds', 'oas', 'oasis'].includes(type) ? type : 'olb';
595
+ const locale = resolveFileViewerLocale(context?.options) === 'en-US' ? 'en-US' : 'zh-CN';
483
596
  const filename = context?.filename || `preview.${normalizedType}`;
484
597
  const root = createElement('section', 'eda-viewer');
485
598
  const style = createStyle();
@@ -493,13 +606,13 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
493
606
  };
494
607
  const showLoading = () => {
495
608
  const state = createElement('div', 'eda-state');
496
- state.append(createElement('span'), createElement('strong', undefined, `正在解析 ${normalizedType.toUpperCase()}...`));
609
+ state.append(createElement('span'), createElement('strong', undefined, formatEdaUiText(locale, 'state.loading', { type: normalizedType.toUpperCase() })));
497
610
  root.append(state);
498
611
  return state;
499
612
  };
500
613
  const showError = (message) => {
501
614
  const error = createElement('div', 'eda-error');
502
- error.append(createElement('strong', undefined, 'EDA 预览提示'), createElement('p', undefined, message));
615
+ error.append(createElement('strong', undefined, formatEdaUiText(locale, 'state.errorTitle')), createElement('p', undefined, message));
503
616
  root.append(error);
504
617
  };
505
618
  const renderParsed = (parsed) => {
@@ -508,27 +621,27 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
508
621
  || parsed.streams.find(stream => stream.kind === 'text')
509
622
  || parsed.streams[0]
510
623
  || null;
511
- const statsCards = buildStatsCards(parsed);
624
+ const statsCards = buildStatsCards(parsed, locale);
512
625
  const treeRows = flattenTree(parsed.tree);
513
- const entityGroups = buildEntityGroups(parsed.entities);
626
+ const entityGroups = buildEntityGroups(parsed.entities, locale);
514
627
  root.replaceChildren();
515
628
  const header = createElement('header', 'eda-header');
516
629
  const headerTitle = document.createElement('div');
517
630
  headerTitle.append(createElement('span', undefined, parsed.parser === 'cfb' ? 'CFB STRUCTURE VIEWER' : 'BINARY STRUCTURE VIEWER'), createElement('h2', undefined, filename));
518
631
  const headerStats = document.createElement('dl');
519
- appendDefinition(headerStats, '格式', parsed.type.toUpperCase());
520
- appendDefinition(headerStats, '大小', formatBytes(parsed.byteLength));
521
- appendDefinition(headerStats, '条目', String(parsed.streamCount));
522
- appendDefinition(headerStats, '可信度', confidenceLabels[parsed.stats.confidence]);
632
+ appendDefinition(headerStats, formatEdaUiText(locale, 'header.format'), parsed.type.toUpperCase());
633
+ appendDefinition(headerStats, formatEdaUiText(locale, 'header.size'), formatBytes(parsed.byteLength));
634
+ appendDefinition(headerStats, formatEdaUiText(locale, 'header.entries'), String(parsed.streamCount));
635
+ appendDefinition(headerStats, formatEdaUiText(locale, 'stats.confidence'), confidenceLabel(parsed.stats.confidence, locale));
523
636
  header.append(headerTitle, headerStats);
524
637
  const body = createElement('div', 'eda-body');
525
638
  const sidebar = createElement('aside', 'eda-sidebar');
526
639
  const summary = createElement('div', 'eda-summary');
527
640
  summary.append(createElement('strong', undefined, parsed.title), createElement('p', undefined, parsed.type === 'gds' || parsed.type === 'oas' || parsed.type === 'oasis'
528
641
  ? parsed.layout
529
- ? `${parsed.layout.format === 'oasis' ? 'OASIS' : 'GDSII'} 属于芯片版图工程文件。预览器已在浏览器端解析可识别几何,小图生成 SVG,大图自动切换 WebGL canvas,同时保留结构、字符串和诊断索引。`
530
- : 'GDSII / OASIS 属于芯片版图工程文件。预览器优先索引结构、属性、可读字符串和二进制线索,并在纯前端安全退化。'
531
- : 'OLB / DRA 属于 OrCAD / Allegro 生态的私有设计数据。预览器优先解析 CFB 结构、对象候选、属性和可读文本,并在纯前端安全退化。'));
642
+ ? formatEdaUiText(locale, 'summary.layoutReady', { layout: parsed.layout.format === 'oasis' ? 'OASIS' : 'GDSII' })
643
+ : formatEdaUiText(locale, 'summary.layoutSafe')
644
+ : formatEdaUiText(locale, 'summary.orcadSafe')));
532
645
  sidebar.append(summary);
533
646
  appendStatGrid(sidebar, statsCards.slice(0, 4), 'eda-mini-grid');
534
647
  if (parsed.warnings.length) {
@@ -538,15 +651,15 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
538
651
  }
539
652
  const search = createElement('input', 'eda-search');
540
653
  search.type = 'search';
541
- search.placeholder = '筛选路径、角色、属性或文本';
654
+ search.placeholder = formatEdaUiText(locale, 'search.placeholder');
542
655
  sidebar.append(search);
543
656
  const streamList = createElement('div', 'eda-stream-list');
544
657
  const preview = createElement('main', 'eda-preview');
545
658
  let streamButtons = [];
546
659
  const currentPanel = createElement('section', 'eda-panel');
547
660
  const selectedHead = createElement('div', 'eda-panel-head');
548
- const selectedTitle = createElement('span', undefined, '当前条目');
549
- const selectedPath = createElement('strong', undefined, '未选择');
661
+ const selectedTitle = createElement('span', undefined, formatEdaUiText(locale, 'selection.title'));
662
+ const selectedPath = createElement('strong', undefined, formatEdaUiText(locale, 'selection.none'));
550
663
  selectedHead.append(selectedTitle, selectedPath);
551
664
  const selectedMeta = createElement('div', 'eda-selected-meta');
552
665
  const selectedProperties = createElement('div', 'eda-property-grid');
@@ -557,16 +670,16 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
557
670
  streamButtons.forEach(({ path, button }) => {
558
671
  button.classList.toggle('active', normalizePath(path) === normalizePath(selectedStream?.path || ''));
559
672
  });
560
- selectedPath.textContent = selectedStream?.path || '未选择';
673
+ selectedPath.textContent = selectedStream?.path || formatEdaUiText(locale, 'selection.none');
561
674
  selectedMeta.replaceChildren();
562
675
  selectedProperties.replaceChildren();
563
676
  selectedPreviewContainer.replaceChildren();
564
677
  localStrings.replaceChildren();
565
678
  if (!selectedStream) {
566
- selectedPreviewContainer.append(createEmpty('目录条目', '该节点用于组织下级流,没有可直接展示的文本或十六进制片段。'));
679
+ selectedPreviewContainer.append(createEmpty(formatEdaUiText(locale, 'selection.storageTitle'), formatEdaUiText(locale, 'selection.storageDescription')));
567
680
  return;
568
681
  }
569
- selectedMeta.append(createElement('span', undefined, roleLabel(selectedStream.role)), createElement('span', undefined, kindLabel(selectedStream.kind)), createElement('span', undefined, formatBytes(selectedStream.size)));
682
+ selectedMeta.append(createElement('span', undefined, roleLabel(selectedStream.role, locale)), createElement('span', undefined, kindLabel(selectedStream.kind, locale)), createElement('span', undefined, formatBytes(selectedStream.size)));
570
683
  selectedStream.properties.forEach(property => {
571
684
  const item = document.createElement('div');
572
685
  item.append(createElement('span', undefined, property.key), createElement('strong', undefined, property.value));
@@ -577,10 +690,10 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
577
690
  selectedPreviewContainer.append(createElement('pre', undefined, previewText));
578
691
  }
579
692
  else {
580
- selectedPreviewContainer.append(createEmpty('目录条目', '该节点用于组织下级流,没有可直接展示的文本或十六进制片段。'));
693
+ selectedPreviewContainer.append(createEmpty(formatEdaUiText(locale, 'selection.storageTitle'), formatEdaUiText(locale, 'selection.storageDescription')));
581
694
  }
582
695
  if (selectedStream.strings.length) {
583
- localStrings.append(createElement('strong', undefined, '当前条目字符串'));
696
+ localStrings.append(createElement('strong', undefined, formatEdaUiText(locale, 'selection.localStrings')));
584
697
  selectedStream.strings.forEach(item => localStrings.append(createElement('span', undefined, item)));
585
698
  }
586
699
  };
@@ -620,9 +733,9 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
620
733
  parsed.streams.filter(stream => matchesFilter(stream, keyword)).forEach(stream => {
621
734
  const button = createElement('button', 'eda-stream');
622
735
  button.type = 'button';
623
- const role = createElement('span', undefined, roleLabel(stream.role));
736
+ const role = createElement('span', undefined, roleLabel(stream.role, locale));
624
737
  role.dataset.role = stream.role;
625
- button.append(role, createElement('strong', undefined, stream.name || stream.path), createElement('em', undefined, stream.path), createElement('small', undefined, `${kindLabel(stream.kind)} · ${formatBytes(stream.size)}`));
738
+ button.append(role, createElement('strong', undefined, stream.name || stream.path), createElement('em', undefined, stream.path), createElement('small', undefined, `${kindLabel(stream.kind, locale)} · ${formatBytes(stream.size)}`));
626
739
  listen(button, 'click', () => selectStream(stream));
627
740
  streamButtons.push({ path: stream.path, button });
628
741
  streamList.append(button);
@@ -632,24 +745,24 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
632
745
  listen(search, 'input', renderStreams);
633
746
  sidebar.append(streamList);
634
747
  const overview = createElement('section', 'eda-panel eda-panel--compact');
635
- appendPanelHead(overview, '解析概览', `${parsed.parser.toUpperCase()} · ${formatBytes(parsed.totalStreamBytes)}`);
748
+ appendPanelHead(overview, formatEdaUiText(locale, 'panel.overview'), `${parsed.parser.toUpperCase()} · ${formatBytes(parsed.totalStreamBytes)}`);
636
749
  appendStatGrid(overview, statsCards, 'eda-stat-grid');
637
750
  const topology = createElement('section', 'eda-topology');
638
751
  const treePanel = createElement('div', 'eda-panel');
639
- appendPanelHead(treePanel, '结构树', `${treeRows.length} 节点`);
752
+ appendPanelHead(treePanel, formatEdaUiText(locale, 'panel.tree'), formatEdaUiText(locale, 'panel.nodeCount', { count: treeRows.length }));
640
753
  const tree = createElement('div', 'eda-tree');
641
754
  treeRows.forEach(row => {
642
755
  const button = createElement('button');
643
756
  button.type = 'button';
644
757
  const twist = createElement('span', undefined, row.children.length ? '▸' : '•');
645
758
  twist.style.paddingLeft = `${row.depth * 14}px`;
646
- button.append(twist, createElement('strong', undefined, row.name), createElement('em', undefined, roleLabel(row.role)), createElement('small', undefined, row.size ? formatBytes(row.size) : kindLabel(row.kind)));
759
+ button.append(twist, createElement('strong', undefined, row.name), createElement('em', undefined, roleLabel(row.role, locale)), createElement('small', undefined, row.size ? formatBytes(row.size) : kindLabel(row.kind, locale)));
647
760
  listen(button, 'click', () => selectTreeRow(row));
648
761
  tree.append(button);
649
762
  });
650
763
  treePanel.append(tree);
651
764
  const entityPanel = createElement('div', 'eda-panel');
652
- appendPanelHead(entityPanel, 'EDA 对象', `${parsed.entities.length} 项`);
765
+ appendPanelHead(entityPanel, formatEdaUiText(locale, 'panel.objects'), formatEdaUiText(locale, 'panel.itemCount', { count: parsed.entities.length }));
653
766
  if (entityGroups.length) {
654
767
  const entityRoot = createElement('div', 'eda-entities');
655
768
  entityGroups.forEach(group => {
@@ -658,7 +771,7 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
658
771
  group.items.forEach(entity => {
659
772
  const button = createElement('button');
660
773
  button.type = 'button';
661
- button.append(createElement('strong', undefined, entity.name), createElement('span', undefined, `${formatBytes(entity.byteLength)} · ${entity.streamCount} 条目`));
774
+ button.append(createElement('strong', undefined, entity.name), createElement('span', undefined, `${formatBytes(entity.byteLength)} · ${formatEdaUiText(locale, 'panel.entryCount', { count: entity.streamCount })}`));
662
775
  if (entity.description) {
663
776
  button.append(createElement('p', undefined, entity.description));
664
777
  }
@@ -683,17 +796,17 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
683
796
  entityPanel.append(entityRoot);
684
797
  }
685
798
  else {
686
- entityPanel.append(createEmpty('没有明确对象候选', '仍可从结构树、属性和字符串索引中查看可读内容。'));
799
+ entityPanel.append(createEmpty(formatEdaUiText(locale, 'empty.noObjects.title'), formatEdaUiText(locale, 'empty.noObjects.description')));
687
800
  }
688
801
  topology.append(treePanel, entityPanel);
689
802
  const bottom = createElement('section', 'eda-bottom');
690
803
  const stringsPanel = createElement('div', 'eda-panel');
691
- appendPanelHead(stringsPanel, '可读字符串', `${parsed.strings.length} 项`);
804
+ appendPanelHead(stringsPanel, formatEdaUiText(locale, 'panel.strings'), formatEdaUiText(locale, 'panel.itemCount', { count: parsed.strings.length }));
692
805
  const stringGrid = createElement('div', 'eda-string-grid');
693
806
  parsed.strings.forEach(item => stringGrid.append(createElement('span', undefined, item)));
694
807
  stringsPanel.append(stringGrid);
695
808
  const diagnosticsPanel = createElement('div', 'eda-panel');
696
- appendPanelHead(diagnosticsPanel, '诊断', `${parsed.diagnostics.length} 条`);
809
+ appendPanelHead(diagnosticsPanel, formatEdaUiText(locale, 'panel.diagnostics'), formatEdaUiText(locale, 'panel.itemCount', { count: parsed.diagnostics.length }));
697
810
  const diagnostics = createElement('div', 'eda-diagnostics');
698
811
  parsed.diagnostics.forEach(diagnostic => {
699
812
  const item = createElement('p');
@@ -704,7 +817,7 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
704
817
  diagnosticsPanel.append(diagnostics, localStrings);
705
818
  bottom.append(stringsPanel, diagnosticsPanel);
706
819
  if (parsed.layout) {
707
- preview.append(createLayoutPreview(parsed.layout));
820
+ preview.append(createLayoutPreview(parsed.layout, locale));
708
821
  }
709
822
  preview.append(overview, topology, currentPanel, bottom);
710
823
  body.append(sidebar, preview);
@@ -713,7 +826,7 @@ export default async function renderEda(buffer, target, type = 'olb', context) {
713
826
  };
714
827
  const loading = showLoading();
715
828
  try {
716
- const parsed = await parseEdaFile(buffer, normalizedType);
829
+ const parsed = await parseEdaFile(buffer, normalizedType, { locale });
717
830
  renderParsed(parsed);
718
831
  }
719
832
  catch (nextError) {
@@ -1,4 +1,5 @@
1
1
  import { type EdaLayoutPreview } from '@file-viewer/eda-layout';
2
+ export type EdaParserLocale = 'zh-CN' | 'en-US';
2
3
  export type { EdaLayoutElement, EdaLayoutPreview, } from '@file-viewer/eda-layout';
3
4
  export type EdaFileType = 'olb' | 'dra' | 'gds' | 'oas' | 'oasis';
4
5
  export type EdaParserMode = 'cfb' | 'binary';
@@ -77,4 +78,6 @@ export interface EdaParseResult {
77
78
  stats: EdaStats;
78
79
  layout?: EdaLayoutPreview;
79
80
  }
80
- export declare const parseEdaFile: (buffer: ArrayBuffer, type?: string) => Promise<EdaParseResult>;
81
+ export declare const parseEdaFile: (buffer: ArrayBuffer, type?: string, options?: {
82
+ locale?: EdaParserLocale;
83
+ }) => Promise<EdaParseResult>;
package/dist/edaParser.js CHANGED
@@ -3,6 +3,40 @@ import { cleanupOrcadText as cleanupText, collectOrcadStrings as collectStrings,
3
3
  const MAX_STREAMS = 200;
4
4
  const MAX_STREAM_STRINGS = 24;
5
5
  const MAX_PROPERTIES = 420;
6
+ const EDA_PARSER_TEXT = {
7
+ 'zh-CN': {
8
+ oasisFixture: '已识别为 OASIS 文本结构夹具,并在浏览器端解析几何预览和结构索引;真实 SEMI 二进制 OASIS 仍走安全索引与后续独立内核路线。',
9
+ gdsLayout: '已识别为标准 GDSII 二进制版图记录,并在浏览器端解析几何预览和结构索引。',
10
+ cfbContainer: '已识别为 Microsoft Compound File / OLE2 复合文档容器,并在浏览器端解析目录与流。',
11
+ binaryIndex: '未识别为 CFB 容器,已使用二进制字符串索引模式展示可读信息。',
12
+ coverage: '已索引 {streams} 个条目、{strings} 个可读字符串、{entities} 个 EDA 结构候选。',
13
+ layout: '已解析 {layout}: {structures} 个 structure、{elements} 个几何/引用/文本元素,可在版图预览面板中拖动查看。',
14
+ noSymbol: '未发现明确的元件符号候选,文件可能使用了私有二进制编码或需要专业工具导出 ASCII/XML 后再检查。',
15
+ noFootprint: '未发现明确的封装、图形或 padstack 候选,文件可能使用了私有二进制数据库编码。',
16
+ noGds: '未发现明确的 GDSII 版图结构候选。文件可能不是标准 GDSII 二进制或使用了专有封装。',
17
+ noOasis: '未发现明确的 OASIS 版图结构候选。OASIS 完整几何浏览通常需要专业版图库或独立 WASM/TS 内核,当前前端包会安全展示头部、字符串、属性和二进制结构线索。',
18
+ maxStreams: '仅展示前 {count} 个 CFB 项,完整文件仍可下载后在专业 EDA 工具中打开。',
19
+ binaryFallback: '该文件不是标准 CFB 容器,已退化为安全的二进制字符串索引预览。',
20
+ },
21
+ 'en-US': {
22
+ oasisFixture: 'Detected an OASIS text fixture and parsed geometry preview plus structure indexes in the browser. Full SEMI binary OASIS still follows the safe index and dedicated-kernel path.',
23
+ gdsLayout: 'Detected standard GDSII binary layout records and parsed geometry preview plus structure indexes in the browser.',
24
+ cfbContainer: 'Detected a Microsoft Compound File / OLE2 container and parsed its directory and streams in the browser.',
25
+ binaryIndex: 'This file is not recognized as a CFB container, so readable information is shown through a safe binary string index.',
26
+ coverage: 'Indexed {streams} entries, {strings} readable strings, and {entities} EDA structure candidates.',
27
+ layout: 'Parsed {layout}: {structures} structures and {elements} geometry/reference/text elements. Drag the layout preview panel to inspect it.',
28
+ noSymbol: 'No clear component-symbol candidates were found. The file may use private binary encoding or need ASCII/XML export from a professional tool.',
29
+ noFootprint: 'No clear footprint, drawing, or padstack candidates were found. The file may use private binary database encoding.',
30
+ noGds: 'No clear GDSII layout structure candidates were found. The file may not be standard GDSII binary or may use a proprietary wrapper.',
31
+ noOasis: 'No clear OASIS layout structure candidates were found. Full OASIS geometry browsing usually needs a professional layout library or dedicated WASM/TS kernel; this frontend package safely exposes headers, strings, properties, and binary clues.',
32
+ maxStreams: 'Only the first {count} CFB entries are shown. Download the full file and open it in a professional EDA tool for complete inspection.',
33
+ binaryFallback: 'This file is not a standard CFB container, so it falls back to a safe binary string index preview.',
34
+ },
35
+ };
36
+ const getEdaParserText = (locale, key, params = {}) => {
37
+ const template = EDA_PARSER_TEXT[locale]?.[key] || EDA_PARSER_TEXT['zh-CN'][key] || key;
38
+ return Object.entries(params).reduce((message, [name, value]) => message.replace(new RegExp(`\\{${name}\\}`, 'g'), String(value)), template);
39
+ };
6
40
  const toBytes = (buffer) => new Uint8Array(buffer);
7
41
  const normalizeBytes = (value) => {
8
42
  return value instanceof Uint8Array ? value : new Uint8Array(value);
@@ -352,7 +386,7 @@ const buildStats = (streams, entities, strings, parser, layout) => {
352
386
  }
353
387
  return stats;
354
388
  };
355
- const buildDiagnostics = (type, parser, streams, entities, strings, warnings, layout) => {
389
+ const buildDiagnostics = (type, parser, streams, entities, strings, warnings, layout, locale) => {
356
390
  const diagnostics = warnings.map((message, index) => ({
357
391
  level: 'warning',
358
392
  code: `warning-${index + 1}`,
@@ -363,22 +397,30 @@ const buildDiagnostics = (type, parser, streams, entities, strings, warnings, la
363
397
  code: 'parser',
364
398
  message: layout
365
399
  ? layout.format === 'oasis'
366
- ? '已识别为 OASIS 文本结构夹具,并在浏览器端解析几何预览和结构索引;真实 SEMI 二进制 OASIS 仍走安全索引与后续独立内核路线。'
367
- : '已识别为标准 GDSII 二进制版图记录,并在浏览器端解析几何预览和结构索引。'
400
+ ? getEdaParserText(locale, 'oasisFixture')
401
+ : getEdaParserText(locale, 'gdsLayout')
368
402
  : parser === 'cfb'
369
- ? '已识别为 Microsoft Compound File / OLE2 复合文档容器,并在浏览器端解析目录与流。'
370
- : '未识别为 CFB 容器,已使用二进制字符串索引模式展示可读信息。'
403
+ ? getEdaParserText(locale, 'cfbContainer')
404
+ : getEdaParserText(locale, 'binaryIndex')
371
405
  });
372
406
  diagnostics.push({
373
407
  level: 'info',
374
408
  code: 'coverage',
375
- message: `已索引 ${streams.length} 个条目、${strings.length} 个可读字符串、${entities.length} 个 EDA 结构候选。`
409
+ message: getEdaParserText(locale, 'coverage', {
410
+ streams: streams.length,
411
+ strings: strings.length,
412
+ entities: entities.length,
413
+ })
376
414
  });
377
415
  if (layout) {
378
416
  diagnostics.push({
379
417
  level: 'info',
380
418
  code: `${layout.format}-layout`,
381
- message: `已解析 ${layout.format === 'oasis' ? 'OASIS 结构夹具' : 'GDSII 版图'}: ${layout.structureCount} 个 structure、${layout.elements.length} 个几何/引用/文本元素,可在版图预览面板中拖动查看。`
419
+ message: getEdaParserText(locale, 'layout', {
420
+ layout: layout.format === 'oasis' ? 'OASIS fixture' : 'GDSII layout',
421
+ structures: layout.structureCount,
422
+ elements: layout.elements.length,
423
+ })
382
424
  });
383
425
  }
384
426
  const needsSymbol = type === 'olb' && !entities.some(entity => entity.role === 'symbol');
@@ -389,21 +431,21 @@ const buildDiagnostics = (type, parser, streams, entities, strings, warnings, la
389
431
  level: 'warning',
390
432
  code: 'domain-candidates',
391
433
  message: type === 'olb'
392
- ? '未发现明确的元件符号候选,文件可能使用了私有二进制编码或需要专业工具导出 ASCII/XML 后再检查。'
434
+ ? getEdaParserText(locale, 'noSymbol')
393
435
  : type === 'dra'
394
- ? '未发现明确的封装、图形或 padstack 候选,文件可能使用了私有二进制数据库编码。'
436
+ ? getEdaParserText(locale, 'noFootprint')
395
437
  : type === 'gds'
396
- ? '未发现明确的 GDSII 版图结构候选。文件可能不是标准 GDSII 二进制或使用了专有封装。'
397
- : '未发现明确的 OASIS 版图结构候选。OASIS 完整几何浏览通常需要专业版图库或独立 WASM/TS 内核,当前前端包会安全展示头部、字符串、属性和二进制结构线索。'
438
+ ? getEdaParserText(locale, 'noGds')
439
+ : getEdaParserText(locale, 'noOasis')
398
440
  });
399
441
  }
400
442
  return diagnostics;
401
443
  };
402
- const assembleResult = (buffer, type, parser, streamCount, streams, strings, warnings, layout) => {
444
+ const assembleResult = (buffer, type, parser, streamCount, streams, strings, warnings, layout, locale) => {
403
445
  const totalStreamBytes = streams.reduce((sum, stream) => sum + stream.size, 0);
404
446
  const entities = collectEntities(streams, type);
405
447
  const metadata = collectMetadata(streams);
406
- const diagnostics = buildDiagnostics(type, parser, streams, entities, strings, warnings, layout);
448
+ const diagnostics = buildDiagnostics(type, parser, streams, entities, strings, warnings, layout, locale);
407
449
  return {
408
450
  type,
409
451
  parser,
@@ -426,7 +468,7 @@ const assembleResult = (buffer, type, parser, streamCount, streams, strings, war
426
468
  layout
427
469
  };
428
470
  };
429
- const parseCfbContainer = async (buffer, type) => {
471
+ const parseCfbContainer = async (buffer, type, locale) => {
430
472
  const CFB = await import('cfb');
431
473
  const container = CFB.parse(toBytes(buffer), { type: 'array' });
432
474
  const streamEntries = container.FileIndex
@@ -443,11 +485,11 @@ const parseCfbContainer = async (buffer, type) => {
443
485
  return buildStreamView(type, path, entry.name, entry.size || content.byteLength || 0, 'binary', content);
444
486
  });
445
487
  const warnings = streamEntries.length >= MAX_STREAMS
446
- ? [`仅展示前 ${MAX_STREAMS} CFB 项,完整文件仍可下载后在专业 EDA 工具中打开。`]
488
+ ? [getEdaParserText(locale, 'maxStreams', { count: MAX_STREAMS })]
447
489
  : [];
448
- return assembleResult(buffer, type, 'cfb', container.FileIndex.length, streams, collectStrings(byteChunks), warnings);
490
+ return assembleResult(buffer, type, 'cfb', container.FileIndex.length, streams, collectStrings(byteChunks), warnings, undefined, locale);
449
491
  };
450
- const parseBinaryFallback = (buffer, type) => {
492
+ const parseBinaryFallback = (buffer, type, locale) => {
451
493
  const bytes = toBytes(buffer);
452
494
  const stream = buildStreamView(type, `${type}.${type}`, `${type}.${type}`, buffer.byteLength, 'binary', bytes);
453
495
  const layout = type === 'gds'
@@ -460,10 +502,11 @@ const parseBinaryFallback = (buffer, type) => {
460
502
  ? layout.warnings
461
503
  : oasis
462
504
  ? oasis.warnings
463
- : ['该文件不是标准 CFB 容器,已退化为安全的二进制字符串索引预览。'];
464
- return assembleResult(buffer, type, 'binary', 1, [stream], collectStrings([bytes]), warnings, layout);
505
+ : [getEdaParserText(locale, 'binaryFallback')];
506
+ return assembleResult(buffer, type, 'binary', 1, [stream], collectStrings([bytes]), warnings, layout, locale);
465
507
  };
466
- export const parseEdaFile = async (buffer, type = 'olb') => {
508
+ export const parseEdaFile = async (buffer, type = 'olb', options = {}) => {
509
+ const locale = options.locale || 'zh-CN';
467
510
  const normalizedType = type === 'dra'
468
511
  ? 'dra'
469
512
  : type === 'gds' || type === 'oas' || type === 'oasis'
@@ -471,13 +514,13 @@ export const parseEdaFile = async (buffer, type = 'olb') => {
471
514
  : 'olb';
472
515
  const bytes = toBytes(buffer);
473
516
  if (!isOrcadCompoundFile(bytes)) {
474
- return parseBinaryFallback(buffer, normalizedType);
517
+ return parseBinaryFallback(buffer, normalizedType, locale);
475
518
  }
476
519
  try {
477
- return await parseCfbContainer(buffer, normalizedType);
520
+ return await parseCfbContainer(buffer, normalizedType, locale);
478
521
  }
479
522
  catch (error) {
480
- const fallback = parseBinaryFallback(buffer, normalizedType);
523
+ const fallback = parseBinaryFallback(buffer, normalizedType, locale);
481
524
  fallback.warnings.unshift(error instanceof Error ? error.message : String(error));
482
525
  fallback.diagnostics.unshift({
483
526
  level: 'warning',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-eda",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone EDA renderer plugin for Flyfish File Viewer with OLB, DRA, GDSII, and OASIS structure preview.",
@@ -57,9 +57,9 @@
57
57
  "LICENSE"
58
58
  ],
59
59
  "dependencies": {
60
- "@file-viewer/core": "^2.1.2",
61
- "@file-viewer/eda-layout": "^2.1.2",
62
- "@file-viewer/eda-orcad": "^2.1.2",
60
+ "@file-viewer/core": "^2.1.4",
61
+ "@file-viewer/eda-layout": "^2.1.4",
62
+ "@file-viewer/eda-orcad": "^2.1.4",
63
63
  "cfb": "^1.2.2"
64
64
  },
65
65
  "devDependencies": {