@stacksjs/defaults 0.75.361 → 0.75.362
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/ai/skills/stacks-ai/SKILL.md +22 -0
- package/ai/skills/stacks-native/CATALOG.md +2 -2
- package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
- package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
- package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
- package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
- package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
- package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
- package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
- package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
- package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
- package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
- package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
- package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
- package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
- package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
- package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
- package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
- package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
- package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
- package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
- package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
- package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
- package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
- package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
- package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
- package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
- package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
- package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
- package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
- package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
- package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
- package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
- package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
- package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
- package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
- package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
- package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
- package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
- package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
- package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
- package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
- package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
- package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
- package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
- package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
- package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
- package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
- package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
- package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
- package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
- package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
- package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
- package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
- package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
- package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
- package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
- package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
- package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
- package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
- package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
- package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
- package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
- package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
- package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
- package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
- package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
- package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
- package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
- package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
- package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
- package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
- package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
- package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
2
4
|
export const DEFAULT_LOCALE = 'en';
|
|
3
5
|
|
|
4
6
|
const ESCAPE_MAP = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
@@ -7,559 +9,290 @@ export function escapeHtml(value) {
|
|
|
7
9
|
return String(value ?? '').replace(/[&<>"']/g, (character) => ESCAPE_MAP[character]);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
'viewer.export.card.routeSummary.one': ['Route: {source} → {target} · {count} directed hop', '路径:{source} → {target} · {count} 个有向跳转'],
|
|
174
|
-
'viewer.export.card.routeSummary.other': ['Route: {source} → {target} · {count} directed hops', '路径:{source} → {target} · {count} 个有向跳转'],
|
|
175
|
-
'viewer.export.card.reachSummary': ['Authored {direction} from {origin} · {nodes} · {links} · max {hops}', '从{origin}开始的编写{direction} · {nodes} · {links} · 最深 {hops}'],
|
|
176
|
-
'viewer.export.card.node.one': ['{count} node', '{count} 个节点'],
|
|
177
|
-
'viewer.export.card.node.other': ['{count} nodes', '{count} 个节点'],
|
|
178
|
-
'viewer.export.card.link.one': ['{count} link', '{count} 条连接'],
|
|
179
|
-
'viewer.export.card.link.other': ['{count} links', '{count} 条连接'],
|
|
180
|
-
'viewer.export.card.hop.one': ['{count} hop', '{count} 跳'],
|
|
181
|
-
'viewer.export.card.hop.other': ['{count} hops', '{count} 跳'],
|
|
182
|
-
'viewer.export.card.routeBadge': ['ARCHIFY · ROUTE · {hops}', 'ARCHIFY · 路径 · {hops}'],
|
|
183
|
-
'viewer.export.card.reachBadge': ['ARCHIFY · {direction} REACH', 'ARCHIFY · {direction}可达范围'],
|
|
184
|
-
'viewer.export.card.defaultBadge': ['ARCHIFY · {preset} · {theme}', 'ARCHIFY · {preset} · {theme}'],
|
|
185
|
-
'viewer.export.direction.upstream': ['Upstream', '上游'],
|
|
186
|
-
'viewer.export.direction.downstream': ['Downstream', '下游'],
|
|
187
|
-
'viewer.export.error.canvasUnavailable': ['Canvas unavailable for {label}', '无法为{label}使用画布'],
|
|
188
|
-
'viewer.export.error.contextUnavailable': ['2D canvas context unavailable for {label}', '无法为{label}创建二维画布上下文'],
|
|
189
|
-
'viewer.export.error.toBlobUnavailable': ['canvas.toBlob unavailable for {label}', '{label}无法使用 canvas.toBlob'],
|
|
190
|
-
'viewer.export.error.toBlobNull': ['canvas.toBlob returned no data for {label}', '{label}的 canvas.toBlob 未返回数据'],
|
|
191
|
-
'viewer.export.error.variantsCombined': ['Share Card variants cannot be combined', '无法同时组合多种分享卡片类型'],
|
|
192
|
-
'viewer.export.error.viewerState': ['Share Card export could not remove temporary viewer state', '分享卡片导出无法移除临时 Viewer 状态'],
|
|
193
|
-
'viewer.export.error.routeState': ['Route Card export could not preserve the resolved route safely', '路径卡片导出无法安全保留已解析路径'],
|
|
194
|
-
'viewer.export.error.reachState': ['Reach Card export could not preserve authored reach safely', '可达范围卡片导出无法安全保留编写的可达范围'],
|
|
195
|
-
'viewer.export.error.webmRequirements': ['WebM motion export requires a trace animation and browser MediaRecorder support', 'WebM 动效导出需要追踪动画及浏览器 MediaRecorder 支持'],
|
|
196
|
-
'viewer.export.error.mediaRecorder': ['MediaRecorder failed', 'MediaRecorder 录制失败'],
|
|
197
|
-
'viewer.export.error.emptyWebm': ['MediaRecorder produced an empty WebM', 'MediaRecorder 生成了空的 WebM'],
|
|
198
|
-
'viewer.export.error.webmBackground': ['SVG background could not be loaded for WebM export', '无法为 WebM 导出加载 SVG 背景'],
|
|
199
|
-
|
|
200
|
-
'viewer.guided.region': ['Guided diagram views', '图表引导视图'],
|
|
201
|
-
'viewer.guided.previous': ['Previous guided view', '上一个引导视图'],
|
|
202
|
-
'viewer.guided.previous.title': ['Previous guided view ([)', '上一个引导视图([)'],
|
|
203
|
-
'viewer.guided.next': ['Next guided view', '下一个引导视图'],
|
|
204
|
-
'viewer.guided.next.title': ['Next guided view (])', '下一个引导视图(])'],
|
|
205
|
-
'viewer.guided.views': ['Guided views', '引导视图'],
|
|
206
|
-
'viewer.guided.explore': ['Explore this system', '探索此系统'],
|
|
207
|
-
'viewer.guided.intro': ['Step through curated paths without changing the source diagram.', '沿精选路径逐步查看,而不改变源图表。'],
|
|
208
|
-
'viewer.guided.trail': ['Story trail', '故事轨迹'],
|
|
209
|
-
'viewer.guided.beat': ['Beat', '节点'],
|
|
210
|
-
'viewer.guided.nextBeat': ['Next', '下一步'],
|
|
211
|
-
'viewer.guided.play': ['Play guided story', '播放引导故事'],
|
|
212
|
-
'viewer.guided.play.title': ['Play guided story (P)', '播放引导故事(P)'],
|
|
213
|
-
'viewer.guided.pause': ['Pause guided story', '暂停引导故事'],
|
|
214
|
-
'viewer.guided.pause.title': ['Pause guided story (P)', '暂停引导故事(P)'],
|
|
215
|
-
'viewer.guided.replay': ['Replay guided story', '重播引导故事'],
|
|
216
|
-
'viewer.guided.replay.title': ['Replay guided story (P)', '重播引导故事(P)'],
|
|
217
|
-
'viewer.guided.playStory': ['Play story', '播放故事'],
|
|
218
|
-
'viewer.guided.pauseStory': ['Pause', '暂停'],
|
|
219
|
-
'viewer.guided.replayStory': ['Replay story', '重播故事'],
|
|
220
|
-
'viewer.guided.motionUnavailable': ['Story playback unavailable while motion is Still', '静态模式下无法播放故事'],
|
|
221
|
-
'viewer.guided.enableMotion': ['Switch motion to Live to play the guided story', '切换为动态模式以播放引导故事'],
|
|
222
|
-
'viewer.guided.selectBeatLink': ['Select a Story Beat to copy its exact link', '选择故事节点以复制其精确链接'],
|
|
223
|
-
'viewer.guided.copyMoment': ['Copy moment', '复制此刻'],
|
|
224
|
-
'viewer.guided.momentCopied': ['Moment link copied', '已复制时刻链接'],
|
|
225
|
-
'viewer.guided.momentCopyFailed': ['Could not copy story moment link', '无法复制故事时刻链接'],
|
|
226
|
-
'viewer.guided.copied': ['Copied', '已复制'],
|
|
227
|
-
'viewer.guided.copyFailed': ['Copy failed', '复制失败'],
|
|
228
|
-
'viewer.guided.showAll': ['Show all', '显示全部'],
|
|
229
|
-
'viewer.guided.showAll.aria': ['Show entire diagram', '显示完整图表'],
|
|
230
|
-
'viewer.guided.chapters': ['Story chapters', '故事章节'],
|
|
231
|
-
'viewer.guided.storyTrail': ['Story trail for {label}: {count} beats', '{label}的故事轨迹:{count} 个节点'],
|
|
232
|
-
'viewer.guided.chapter.open': ['Open chapter {index} of {total}: {label}, {count} stops', '打开第 {index}/{total} 章:{label},{count} 个停靠点'],
|
|
233
|
-
'viewer.guided.chapter.current': ['Current chapter {index} of {total}: {label}, {count} stops', '当前第 {index}/{total} 章:{label},{count} 个停靠点'],
|
|
234
|
-
'viewer.guided.chapter.selectedNodes': ['{count} selected nodes', '已选择 {count} 个节点'],
|
|
235
|
-
'viewer.guided.chapter.stops': ['{count} stops', '{count} 个停靠点'],
|
|
236
|
-
'viewer.guided.chapter.stop.one': ['{count} stop', '{count} 个停靠点'],
|
|
237
|
-
'viewer.guided.chapter.stop.other': ['{count} stops', '{count} 个停靠点'],
|
|
238
|
-
'viewer.guided.chapter.current.title': ['{label} - current chapter, {count} stops', '{label} - 当前章节,{count} 个停靠点'],
|
|
239
|
-
'viewer.guided.chapter.delta.expanded': ['{stay} stay, {enter} enter, {leave} leave', '{stay} 个保留,{enter} 个进入,{leave} 个离开'],
|
|
240
|
-
'viewer.guided.chapter.delta.aria': ['Open chapter {index} of {total}: {label}. Chapter focus delta: {delta}', '打开第 {index}/{total} 章:{label}。章节聚焦差异:{delta}'],
|
|
241
|
-
'viewer.guided.chapter.delta.title': ['{label} - {delta} chapter focus', '{label} - 章节聚焦 {delta}'],
|
|
242
|
-
'viewer.guided.handoff': ['{from} → {to} · via {label}', '{from} → {to} · 经由{label}'],
|
|
243
|
-
'viewer.guided.share.chapter': ['Chapter {index} / {total}', '章节 {index} / {total}'],
|
|
244
|
-
'viewer.guided.share.initial': ['Chapter 01 / 01', '章节 01 / 01'],
|
|
245
|
-
'viewer.guided.share.default': ['Guided chapter', '引导章节'],
|
|
246
|
-
'viewer.guided.state.ready': ['Ready', '就绪'],
|
|
247
|
-
'viewer.guided.state.playing': ['Playing', '播放中'],
|
|
248
|
-
'viewer.guided.state.settled': ['Settled', '已完成'],
|
|
249
|
-
'viewer.guided.state.paused': ['Paused', '已暂停'],
|
|
250
|
-
'viewer.guided.state.pinned': ['Pinned', '已固定'],
|
|
251
|
-
'viewer.guided.state.still': ['Still', '静态'],
|
|
252
|
-
'viewer.guided.share.step': ['Step {index} / {total} · {label}', '步骤 {index} / {total} · {label}'],
|
|
253
|
-
'viewer.guided.share.staticMoment': ['{step} · Static moment', '{step} · 静态时刻'],
|
|
254
|
-
'viewer.guided.share.complete': ['{count} steps complete · {note}', '{count} 个步骤已完成 · {note}'],
|
|
255
|
-
'viewer.guided.share.settled': ['Path settled for reading.', '路径已稳定,可供阅读。'],
|
|
256
|
-
'viewer.guided.share.staticPath': ['{count} steps · Static path', '{count} 个步骤 · 静态路径'],
|
|
257
|
-
'viewer.guided.share.ready': ['{count} steps · Ready', '{count} 个步骤 · 就绪'],
|
|
258
|
-
'viewer.guided.share.aria': ['{state} chapter {index} of {total}: {label}. {beat}. {route}', '{state},第 {index}/{total} 章:{label}。{beat}。{route}'],
|
|
259
|
-
'viewer.guided.beat.start': ['Beat {index} / {total} · {label} · starting point', '节点 {index} / {total} · {label} · 起点'],
|
|
260
|
-
'viewer.guided.beat.forward': ['Beat {index} / {total} · {from} → {to}', '节点 {index} / {total} · {from} → {to}'],
|
|
261
|
-
'viewer.guided.beat.reverse': ['Beat {index} / {total} · {from} → {to} · reverse authored link', '节点 {index} / {total} · {from} → {to} · 反向编写连接'],
|
|
262
|
-
'viewer.guided.beat.multiple': ['Beat {index} / {total} · {from} ⇄ {to} · {count} authored links', '节点 {index} / {total} · {from} ⇄ {to} · {count} 条编写连接'],
|
|
263
|
-
'viewer.guided.beat.group': ['Beat {index} / {total} · {from} · {to} · grouped · no direct link', '节点 {index} / {total} · {from} · {to} · 分组 · 无直接连接'],
|
|
264
|
-
'viewer.guided.beat.aria.prefix': ['Story beat {index} of {total}: {label}. ', '故事节点 {index}/{total}:{label}。'],
|
|
265
|
-
'viewer.guided.beat.aria.start': ['Starting point.', '起点。'],
|
|
266
|
-
'viewer.guided.beat.aria.forward': ['From {from} through one authored forward relationship.', '从{from}经一条正向编写关系到达。'],
|
|
267
|
-
'viewer.guided.beat.aria.reverse': ['From {from}; the authored relationship points from {to} to {from}.', '从{from}出发;编写关系实际由{to}指向{from}。'],
|
|
268
|
-
'viewer.guided.beat.aria.multiple': ['From {from} through {count} authored relationships; shown without arbitrary motion.', '从{from}经 {count} 条编写关系到达;不使用任意动效。'],
|
|
269
|
-
'viewer.guided.beat.aria.group': ['Grouped from {from} with no direct authored relationship.', '与{from}分组展示,没有直接编写关系。'],
|
|
270
|
-
'viewer.guided.caption.start': ['Starting point', '起点'],
|
|
271
|
-
'viewer.guided.caption.grouped': ['Grouped transition · no direct authored link', '分组过渡 · 无直接编写连接'],
|
|
272
|
-
'viewer.guided.caption.more': [' +{count} more', ' +另外 {count} 条'],
|
|
273
|
-
'viewer.guided.caption.reverse': ['Reverse authored relationship', '反向编写关系'],
|
|
274
|
-
'viewer.guided.caption.relationships': ['{count} authored relationships', '{count} 条编写关系'],
|
|
275
|
-
'viewer.guided.caption.relationship': ['Authored relationship', '编写关系'],
|
|
276
|
-
'viewer.guided.caption.direction': ['authored direction: {from} → {to}', '编写方向:{from} → {to}'],
|
|
277
|
-
'viewer.guided.caption.starting': ['Authored starting point', '编写起点'],
|
|
278
|
-
'viewer.guided.beatLink': ['Copy link to current story moment: Beat {index} of {total}: {label}', '复制当前故事时刻链接:第 {index}/{total} 个节点:{label}'],
|
|
279
|
-
'viewer.guided.noStory': ['This diagram has no authored guided story.', '此图表没有编写引导故事。'],
|
|
280
|
-
|
|
281
|
-
'viewer.guide.eyebrow': ['Diagram guide', '图表指南'],
|
|
282
|
-
'viewer.guide.close': ['Close diagram guide', '关闭图表指南'],
|
|
283
|
-
'viewer.guide.inspecting': ['Inspecting compiled semantics', '正在检查已编译语义'],
|
|
284
|
-
'viewer.guide.actions': ['Diagram exploration actions', '图表探索操作'],
|
|
285
|
-
'viewer.guide.find': ['Find any node', '查找任意节点'],
|
|
286
|
-
'viewer.guide.find.hint': ['Search labels, responsibilities, kinds, and stable IDs.', '搜索标签、职责、类型和稳定 ID。'],
|
|
287
|
-
'viewer.guide.route': ['Trace a route', '追踪路径'],
|
|
288
|
-
'viewer.guide.route.aria': ['Trace a directed route', '追踪有向路径'],
|
|
289
|
-
'viewer.guide.route.hint': ['Ask how two semantic nodes connect in authored direction.', '查看两个语义节点如何按编写方向连接。'],
|
|
290
|
-
'viewer.guide.map': ['See the whole system', '查看完整系统'],
|
|
291
|
-
'viewer.guide.map.hint': ['Open Semantic Radar with a live viewport and stable nodes.', '打开带实时视口和稳定节点的语义雷达。'],
|
|
292
|
-
'viewer.guide.lens': ['Compare semantic kinds', '比较语义类型'],
|
|
293
|
-
'viewer.guide.lens.hint': ['Count roles, reveal their traffic, and compare direct authored links.', '统计角色、显示流量并比较直接编写的连接。'],
|
|
294
|
-
'viewer.guide.story': ['Play the guided story', '播放引导故事'],
|
|
295
|
-
'viewer.guide.story.hint': ['Walk the authored chapters and real relationships.', '浏览已编写的章节和真实关系。'],
|
|
296
|
-
'viewer.guide.present': ['Enter Presentation Stage', '进入演示模式'],
|
|
297
|
-
'viewer.guide.present.hint': ['Give the live diagram the viewport without changing export.', '让实时图表占满视口,同时不改变导出。'],
|
|
298
|
-
'viewer.guide.shortcuts': ['Additional keyboard shortcuts', '其他键盘快捷键'],
|
|
299
|
-
'viewer.guide.shortcut.export': ['Export', '导出'],
|
|
300
|
-
'viewer.guide.shortcut.theme': ['Theme', '主题'],
|
|
301
|
-
'viewer.guide.shortcut.style': ['Style', '风格'],
|
|
302
|
-
'viewer.guide.shortcut.reset': ['Reset', '重置'],
|
|
303
|
-
'viewer.guide.shortcut.zoomIn': ['Zoom in', '放大'],
|
|
304
|
-
'viewer.guide.shortcut.zoomOut': ['Zoom out', '缩小'],
|
|
305
|
-
'viewer.guide.shortcut.close': ['Close', '关闭'],
|
|
306
|
-
'viewer.guide.facts': ['{nodes} · {relationships} · {views}', '{nodes} · {relationships} · {views}'],
|
|
307
|
-
'viewer.guide.fact.node.one': ['{count} semantic node', '{count} 个语义节点'],
|
|
308
|
-
'viewer.guide.fact.node.other': ['{count} semantic nodes', '{count} 个语义节点'],
|
|
309
|
-
'viewer.guide.fact.relationship.one': ['{count} relationship', '{count} 条关系'],
|
|
310
|
-
'viewer.guide.fact.relationship.other': ['{count} relationships', '{count} 条关系'],
|
|
311
|
-
'viewer.guide.fact.view.one': ['{count} guided view', '{count} 个引导视图'],
|
|
312
|
-
'viewer.guide.fact.view.other': ['{count} guided views', '{count} 个引导视图'],
|
|
313
|
-
'viewer.guide.story.available.one': ['Walk {count} authored chapter and its real relationships.', '浏览 {count} 个已编写章节及其真实关系。'],
|
|
314
|
-
'viewer.guide.story.available.other': ['Walk {count} authored chapters and their real relationships.', '浏览 {count} 个已编写章节及其真实关系。'],
|
|
315
|
-
'viewer.guide.story.unavailable': ['No authored guided story in this diagram.', '此图表没有编写引导故事。'],
|
|
316
|
-
'viewer.guide.open': ['Open diagram guide', '打开图表指南'],
|
|
317
|
-
'viewer.guide.noStory': ['This diagram has no authored guided story.', '此图表没有编写引导故事。'],
|
|
318
|
-
|
|
319
|
-
'viewer.finder.title': ['Find a node', '查找节点'],
|
|
320
|
-
'viewer.finder.close': ['Close node finder', '关闭节点查找器'],
|
|
321
|
-
'viewer.finder.placeholder': ['Search labels or IDs', '搜索标签或 ID'],
|
|
322
|
-
'viewer.finder.search': ['Search diagram nodes', '搜索图表节点'],
|
|
323
|
-
'viewer.finder.results': ['Diagram nodes', '图表节点'],
|
|
324
|
-
'viewer.finder.empty': ['No matching nodes', '没有匹配的节点'],
|
|
325
|
-
'viewer.finder.result.focus': ['Focus {label}', '聚焦{label}'],
|
|
326
|
-
'viewer.finder.result.routeStart': ['Choose {label} as route start', '选择{label}作为路径起点'],
|
|
327
|
-
'viewer.finder.result.routeTarget': ['Choose {label} as route destination, {links}', '选择{label}作为路径终点,{links}'],
|
|
328
|
-
'viewer.finder.status.empty': ['No matching nodes', '没有匹配的节点'],
|
|
329
|
-
'viewer.finder.status.count.one': ['{count} matching node', '{count} 个匹配节点'],
|
|
330
|
-
'viewer.finder.status.count.other': ['{count} matching nodes', '{count} 个匹配节点'],
|
|
331
|
-
'viewer.finder.noun.nodes': ['nodes', '个节点'],
|
|
332
|
-
'viewer.finder.link.one': ['{count} link', '{count} 条连接'],
|
|
333
|
-
'viewer.finder.link.other': ['{count} links', '{count} 条连接'],
|
|
334
|
-
'viewer.finder.result.focus.one': ['Focus {label}, {count} related connection', '聚焦{label},{count} 条相关连接'],
|
|
335
|
-
'viewer.finder.result.focus.other': ['Focus {label}, {count} related connections', '聚焦{label},{count} 条相关连接'],
|
|
336
|
-
'viewer.finder.status.filtered': ['{visible} of {available} {noun}', '{visible}/{available} {noun}'],
|
|
337
|
-
'viewer.finder.status.all': ['{available} {noun}', '{available} {noun}'],
|
|
338
|
-
|
|
339
|
-
'viewer.passport.eyebrow': ['Semantic passport', '语义护照'],
|
|
340
|
-
'viewer.passport.metadata': ['Node metadata', '节点元数据'],
|
|
341
|
-
'viewer.passport.evidence': ['Verified source evidence', '已验证的源代码证据'],
|
|
342
|
-
'viewer.passport.verified': ['Verified source', '已验证来源'],
|
|
343
|
-
'viewer.passport.verificationScope': ['Verified against local Git at the pinned revision. Remote access has not been checked.', '已按固定修订版本验证本地 Git 证据,未检查远程访问权限。'],
|
|
344
|
-
'viewer.passport.reach': ['Authored reach', '编写可达范围'],
|
|
345
|
-
'viewer.passport.reach.trace': ['Trace authored reachability', '追踪编写的可达性'],
|
|
346
|
-
'viewer.passport.upstream': ['Upstream', '上游'],
|
|
347
|
-
'viewer.passport.downstream': ['Downstream', '下游'],
|
|
348
|
-
'viewer.passport.upstream.trace': ['Trace upstream authored reachability', '追踪上游编写可达性'],
|
|
349
|
-
'viewer.passport.downstream.trace': ['Trace downstream authored reachability', '追踪下游编写可达性'],
|
|
350
|
-
'viewer.passport.close': ['Close semantic passport', '关闭语义护照'],
|
|
351
|
-
'viewer.passport.copy': ['Copy link', '复制链接'],
|
|
352
|
-
'viewer.passport.copy.focus': ['Copy link to focused node', '复制聚焦节点的链接'],
|
|
353
|
-
'viewer.passport.relations': ['Relations', '关系'],
|
|
354
|
-
'viewer.passport.relations.show': ['Show connected relationships', '显示关联关系'],
|
|
355
|
-
'viewer.passport.relations.hide': ['Hide connected relationships', '隐藏关联关系'],
|
|
356
|
-
'viewer.passport.relations.list': ['Connected relationships', '关联关系'],
|
|
357
|
-
'viewer.passport.copyRelation': ['Copy relation', '复制关系'],
|
|
358
|
-
'viewer.passport.copyNode': ['Copy node', '复制节点'],
|
|
359
|
-
'viewer.passport.copyPinned': ['Copy link to pinned relationship', '复制固定关系的链接'],
|
|
360
|
-
'viewer.passport.copySource': ['Copy link to source node', '复制来源节点的链接'],
|
|
361
|
-
'viewer.passport.copy.focused.success': ['Focused node link copied', '已复制聚焦节点链接'],
|
|
362
|
-
'viewer.passport.copy.pinned.success': ['Pinned relationship link copied', '已复制固定关系链接'],
|
|
363
|
-
'viewer.passport.copy.focused.failed': ['Could not copy focused node link', '无法复制聚焦节点链接'],
|
|
364
|
-
'viewer.passport.copy.pinned.failed': ['Could not copy pinned relationship link', '无法复制固定关系链接'],
|
|
365
|
-
'viewer.passport.relationship.none': ['No connected relationships', '没有关联关系'],
|
|
366
|
-
'viewer.passport.relationship.count.one': ['{count} relation', '{count} 条关系'],
|
|
367
|
-
'viewer.passport.relationship.count.other': ['{count} relations', '{count} 条关系'],
|
|
368
|
-
'viewer.passport.relationship.show.one': ['Show {count} connected relationship', '显示 {count} 条关联关系'],
|
|
369
|
-
'viewer.passport.relationship.show.other': ['Show {count} connected relationships', '显示 {count} 条关联关系'],
|
|
370
|
-
'viewer.passport.relationship.summary': ['{out} outgoing · {in} incoming{loops}', '{out} 条出向 · {in} 条入向{loops}'],
|
|
371
|
-
'viewer.passport.relationship.loops': [' · {count} loop', ' · {count} 条自环'],
|
|
372
|
-
'viewer.passport.relationship.explorer': ['Direct relationship explorer', '直接关系浏览器'],
|
|
373
|
-
'viewer.passport.relationship.help': ['Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.', '使用方向键浏览关系。按 Enter 或空格键固定详情;按 Escape 清除。'],
|
|
374
|
-
'viewer.passport.relationship.loopsBack': ['loops back', '回环'],
|
|
375
|
-
'viewer.passport.relationship.connectsTo': ['connects to', '连接到'],
|
|
376
|
-
'viewer.passport.relationship.connectsFrom': ['connects from', '连接自'],
|
|
377
|
-
'viewer.passport.relationship.pinned': ['Pinned relationship · {from} → {to} · {label}', '已固定关系 · {from} → {to} · {label}'],
|
|
378
|
-
'viewer.passport.relationship.inspect': ['Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.', '检查第 {index}/{total} 条关系:{from} 到 {to},{label}。按 Enter 查看详情。'],
|
|
379
|
-
'viewer.passport.relationship.group.out': ['Outgoing', '出向'],
|
|
380
|
-
'viewer.passport.relationship.group.in': ['Incoming', '入向'],
|
|
381
|
-
'viewer.passport.relationship.group.loop': ['Self loops', '自环'],
|
|
382
|
-
'viewer.passport.relationship.row': ['{group}: {relationship}, {neighbor}', '{group}:{relationship},{neighbor}'],
|
|
383
|
-
'viewer.passport.relationship.direction.out': ['OUT →', '出 →'],
|
|
384
|
-
'viewer.passport.relationship.direction.in': ['← IN', '← 入'],
|
|
385
|
-
'viewer.passport.relationship.direction.loop': ['LOOP', '自环'],
|
|
386
|
-
'viewer.passport.sourceCount.one': ['{count} verified source reference', '{count} 个已验证来源引用'],
|
|
387
|
-
'viewer.passport.sourceCount.other': ['{count} verified source references', '{count} 个已验证来源引用'],
|
|
388
|
-
'viewer.passport.sourceMarker': ['SRC', '来源'],
|
|
389
|
-
'viewer.passport.beacon.one': ['{count} verified source; focus this node to inspect', '{count} 个已验证来源;聚焦此节点以检查'],
|
|
390
|
-
'viewer.passport.beacon.other': ['{count} verified sources; focus this node to inspect', '{count} 个已验证来源;聚焦此节点以检查'],
|
|
391
|
-
'viewer.passport.repository.open': ['Open verified repository revision {revision}', '打开已验证的仓库修订版本 {revision}'],
|
|
392
|
-
'viewer.passport.source.open': ['Open verified source {path} at revision {revision}', '打开修订版本 {revision} 中已验证的来源 {path}'],
|
|
393
|
-
'viewer.passport.source.openLink': ['Open ↗', '打开 ↗'],
|
|
394
|
-
'viewer.passport.reach.upstream.one': ['Trace {count} upstream authored node', '追踪 {count} 个上游编写节点'],
|
|
395
|
-
'viewer.passport.reach.upstream.other': ['Trace {count} upstream authored nodes', '追踪 {count} 个上游编写节点'],
|
|
396
|
-
'viewer.passport.reach.downstream.one': ['Trace {count} downstream authored node', '追踪 {count} 个下游编写节点'],
|
|
397
|
-
'viewer.passport.reach.downstream.other': ['Trace {count} downstream authored nodes', '追踪 {count} 个下游编写节点'],
|
|
398
|
-
'viewer.passport.reach.noUpstream': ['No upstream authored nodes', '没有上游编写节点'],
|
|
399
|
-
'viewer.passport.reach.noDownstream': ['No downstream authored nodes', '没有下游编写节点'],
|
|
400
|
-
'viewer.passport.reach.status': ['{direction} · {nodes} nodes · {links} links · max {hops} hops', '{direction} · {nodes} 个节点 · {links} 条连接 · 最深 {hops} 跳'],
|
|
401
|
-
|
|
402
|
-
'viewer.route.eyebrow': ['Route probe', '路径探测'],
|
|
403
|
-
'viewer.route.start': ['Choose a start node', '选择起点节点'],
|
|
404
|
-
'viewer.route.start.find': ['Find start', '查找起点'],
|
|
405
|
-
'viewer.route.start.find.aria': ['Find a route start', '查找路径起点'],
|
|
406
|
-
'viewer.route.copy': ['Copy link', '复制链接'],
|
|
407
|
-
'viewer.route.copy.aria': ['Copy link to traced route', '复制已追踪路径的链接'],
|
|
408
|
-
'viewer.route.clear': ['Clear', '清除'],
|
|
409
|
-
'viewer.route.clear.aria': ['Clear route probe', '清除路径探测'],
|
|
410
|
-
'viewer.route.traced': ['Traced route', '已追踪路径'],
|
|
411
|
-
'viewer.route.pickTwo': ['Pick two semantic nodes on the diagram', '在图表中选择两个语义节点'],
|
|
412
|
-
'viewer.route.pickOne': ['Pick a semantic node on the diagram', '在图表中选择一个语义节点'],
|
|
413
|
-
'viewer.route.controls': ['Route journey controls', '路径旅程控制'],
|
|
414
|
-
'viewer.route.previous': ['Previous route position', '上一个路径位置'],
|
|
415
|
-
'viewer.route.play': ['Play route journey', '播放路径旅程'],
|
|
416
|
-
'viewer.route.pause': ['Pause route journey', '暂停路径旅程'],
|
|
417
|
-
'viewer.route.replay': ['Replay route journey', '重播路径旅程'],
|
|
418
|
-
'viewer.route.next': ['Next route position', '下一个路径位置'],
|
|
419
|
-
'viewer.route.journey': ['Journey', '旅程'],
|
|
420
|
-
'viewer.route.pause.label': ['Pause', '暂停'],
|
|
421
|
-
'viewer.route.replay.label': ['Replay', '重播'],
|
|
422
|
-
'viewer.route.overview': ['Overview', '总览'],
|
|
423
|
-
'viewer.route.overview.aria': ['Show complete route overview', '显示完整路径总览'],
|
|
424
|
-
'viewer.route.instructions': ['Choose the source, then the destination. Direction matters.', '先选择来源,再选择目标;方向很重要。'],
|
|
425
|
-
'viewer.route.destination': ['Choose a destination from {label}', '选择从{label}出发的目标'],
|
|
426
|
-
'viewer.route.destination.find': ['Find target', '查找目标'],
|
|
427
|
-
'viewer.route.destination.find.aria': ['Find a reachable route destination', '查找可达的路径目标'],
|
|
428
|
-
'viewer.route.differentDestination': ['Choose a different destination', '选择其他目标'],
|
|
429
|
-
'viewer.route.distinct': ['A route needs two distinct semantic nodes.', '一条路径需要两个不同的语义节点。'],
|
|
430
|
-
'viewer.route.unreachable': ['No directed route to {label}', '没有通往{label}的有向路径'],
|
|
431
|
-
'viewer.route.unreachable.detail': ['{target} is not reachable from {source}. Pick a highlighted destination.', '从{source}无法到达{target}。请选择高亮的目标。'],
|
|
432
|
-
'viewer.route.start.instructions': ['Select the source. The next step will reveal only directed destinations.', '选择来源。下一步只会显示有向可达的目标。'],
|
|
433
|
-
'viewer.route.copy.success': ['Traced route link copied', '已复制路径链接'],
|
|
434
|
-
'viewer.route.copy.failed': ['Could not copy traced route link', '无法复制路径链接'],
|
|
435
|
-
'viewer.route.position': ['Route position {index} of {total}: {label}', '路径位置 {index}/{total}:{label}'],
|
|
436
|
-
'viewer.route.step': ['Step {index} of {total} · {phase} · {label}', '第 {index}/{total} 步 · {phase} · {label}'],
|
|
437
|
-
'viewer.route.motionRequired': ['Automatic journey requires Live motion', '自动旅程需要动态模式'],
|
|
438
|
-
'viewer.route.trigger.clear': ['Clear traced route', '清除已追踪路径'],
|
|
439
|
-
'viewer.route.overview.status': ['{nodes} · {hops} · shortest authored route', '{nodes} · {hops} · 最短编写路径'],
|
|
440
|
-
'viewer.route.overview.node.one': ['{count} node', '{count} 个节点'],
|
|
441
|
-
'viewer.route.overview.node.other': ['{count} nodes', '{count} 个节点'],
|
|
442
|
-
'viewer.route.overview.hop.one': ['{count} directed hop', '{count} 个有向跳转'],
|
|
443
|
-
'viewer.route.overview.hop.other': ['{count} directed hops', '{count} 个有向跳转'],
|
|
444
|
-
'viewer.route.phase.playing': ['Playing', '播放中'],
|
|
445
|
-
'viewer.route.phase.complete': ['Complete', '已完成'],
|
|
446
|
-
'viewer.route.phase.inspecting': ['Inspecting', '检查中'],
|
|
447
|
-
'viewer.route.destination.count.one': ['{count} directed destination available. Pick a highlighted node.', '有 {count} 个有向目标可用。请选择高亮节点。'],
|
|
448
|
-
'viewer.route.destination.count.other': ['{count} directed destinations available. Pick a highlighted node.', '有 {count} 个有向目标可用。请选择高亮节点。'],
|
|
449
|
-
'viewer.route.noOutgoing': ['No outgoing route starts here. Clear and choose another source.', '此处没有可用的出向路径。请清除后选择其他来源。'],
|
|
450
|
-
'viewer.route.result.title': ['{source} to {target}', '{source} 到 {target}'],
|
|
451
|
-
'viewer.route.finder.source.title': ['Choose route start', '选择路径起点'],
|
|
452
|
-
'viewer.route.finder.source.placeholder': ['Search route sources', '搜索路径来源'],
|
|
453
|
-
'viewer.route.finder.source.empty': ['No matching route sources', '没有匹配的路径来源'],
|
|
454
|
-
'viewer.route.finder.source.results': ['Nodes that can start a route', '可作为路径起点的节点'],
|
|
455
|
-
'viewer.route.finder.source.noun': ['route sources', '个路径来源'],
|
|
456
|
-
'viewer.route.finder.source.badge': ['start', '起点'],
|
|
457
|
-
'viewer.route.finder.target.title': ['Destination from {label}', '从{label}出发的目标'],
|
|
458
|
-
'viewer.route.finder.target.placeholder': ['Search reachable destinations', '搜索可达目标'],
|
|
459
|
-
'viewer.route.finder.target.empty': ['No matching reachable destinations', '没有匹配的可达目标'],
|
|
460
|
-
'viewer.route.finder.target.results': ['Reachable route destinations', '可达路径目标'],
|
|
461
|
-
'viewer.route.finder.target.noun': ['reachable destinations', '个可达目标'],
|
|
462
|
-
'viewer.route.hop.one': ['{count} hop', '{count} 跳'],
|
|
463
|
-
'viewer.route.hop.other': ['{count} hops', '{count} 跳'],
|
|
464
|
-
|
|
465
|
-
'viewer.lens.eyebrow': ['Semantic lens', '语义透镜'],
|
|
466
|
-
'viewer.lens.title': ['Compare system roles', '比较系统角色'],
|
|
467
|
-
'viewer.lens.close': ['Close semantic lens', '关闭语义透镜'],
|
|
468
|
-
'viewer.lens.instruction': ['Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.', '最多选择两种语义类型。选择一种可显示其真实流量;选择两种只比较直接编写的关系。'],
|
|
469
|
-
'viewer.lens.kinds': ['Semantic kinds', '语义类型'],
|
|
470
|
-
'viewer.lens.choose': ['Choose a kind to inspect its nodes and touching relationships.', '选择一种类型以检查其节点和相连关系。'],
|
|
471
|
-
'viewer.lens.copy': ['Copy link to semantic lens', '复制语义透镜链接'],
|
|
472
|
-
'viewer.lens.clear': ['Clear semantic lens', '清除语义透镜'],
|
|
473
|
-
'viewer.lens.open': ['Open semantic lens', '打开语义透镜'],
|
|
474
|
-
'viewer.lens.openActive': ['Open active semantic lens', '打开当前语义透镜'],
|
|
475
|
-
'viewer.lens.legend': ['Semantic legend', '语义图例'],
|
|
476
|
-
'viewer.lens.legend.inspect.one': ['Inspect {label}, {count} node', '检查{label},{count} 个节点'],
|
|
477
|
-
'viewer.lens.legend.inspect.other': ['Inspect {label}, {count} nodes', '检查{label},{count} 个节点'],
|
|
478
|
-
'viewer.lens.kind.count.one': ['{label}, {count} node', '{label},{count} 个节点'],
|
|
479
|
-
'viewer.lens.kind.count.other': ['{label}, {count} nodes', '{label},{count} 个节点'],
|
|
480
|
-
'viewer.lens.compare.one': ['{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship', '{first} → {second}:{forward} · {second} → {first}:{reverse} · 共 {count} 条直接关系'],
|
|
481
|
-
'viewer.lens.compare.other': ['{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships', '{first} → {second}:{forward} · {second} → {first}:{reverse} · 共 {count} 条直接关系'],
|
|
482
|
-
'viewer.lens.single': ['{nodes} · {relationships} · connected peers remain visible', '{nodes} · {relationships} · 已连接节点保持可见'],
|
|
483
|
-
'viewer.lens.node.one': ['{count} {label} node', '{count} 个{label}节点'],
|
|
484
|
-
'viewer.lens.node.other': ['{count} {label} nodes', '{count} 个{label}节点'],
|
|
485
|
-
'viewer.lens.relationship.one': ['{count} touching relationship', '{count} 条相连关系'],
|
|
486
|
-
'viewer.lens.relationship.other': ['{count} touching relationships', '{count} 条相连关系'],
|
|
487
|
-
|
|
488
|
-
'viewer.radar.title': ['Semantic radar', '语义雷达'],
|
|
489
|
-
'viewer.radar.building': ['Building overview', '正在构建总览'],
|
|
490
|
-
'viewer.radar.openFull': ['Open full semantic radar', '打开完整语义雷达'],
|
|
491
|
-
'viewer.radar.open': ['Open radar', '打开雷达'],
|
|
492
|
-
'viewer.radar.close': ['Close semantic radar', '关闭语义雷达'],
|
|
493
|
-
'viewer.radar.surface': ['Diagram overview. Click a node to focus it, or use arrow keys to pan.', '图表总览。点击节点进行聚焦,或使用方向键平移。'],
|
|
494
|
-
'viewer.radar.click': ['Click node', '点击节点'],
|
|
495
|
-
'viewer.radar.drag': ['Drag to pan', '拖动平移'],
|
|
496
|
-
'viewer.radar.space': ['Semantic radar needs more MAP space.', '语义雷达需要更多地图可见空间。'],
|
|
497
|
-
'viewer.radar.nodes': ['Semantic diagram radar nodes', '语义图表雷达节点'],
|
|
498
|
-
'viewer.radar.focus': ['Focus {label} from Semantic Radar', '从语义雷达聚焦{label}'],
|
|
499
|
-
'viewer.radar.status': ['{count} nodes · {viewport}', '{count} 个节点 · {viewport}'],
|
|
500
|
-
'viewer.radar.fullMap': ['{count} nodes · full map', '{count} 个节点 · 完整地图'],
|
|
501
|
-
'viewer.radar.compacted': ['Radar compacted to avoid covering the Semantic Passport or MAP controls.', '已收紧雷达,避免遮挡语义护照或地图控件。'],
|
|
502
|
-
'viewer.radar.cancelWaiting': ['Cancel semantic radar waiting for more MAP space', '取消等待更多地图空间的语义雷达'],
|
|
503
|
-
'viewer.radar.needsSpace': ['Semantic radar needs more visible MAP space', '语义雷达需要更多可见地图空间'],
|
|
504
|
-
'viewer.radar.viewport.full': ['full map', '完整地图'],
|
|
505
|
-
'viewer.radar.viewport.width': ['{percent}% width', '宽度 {percent}%'],
|
|
506
|
-
'viewer.radar.viewport.scale': ['{percent}% viewport', '视口 {percent}%'],
|
|
507
|
-
|
|
508
|
-
'viewer.nav.controls': ['Diagram view controls', '图表视图控制'],
|
|
509
|
-
'viewer.nav.route': ['Trace a directed route', '追踪有向路径'],
|
|
510
|
-
'viewer.nav.route.title': ['Trace route (R)', '追踪路径(R)'],
|
|
511
|
-
'viewer.nav.route.short': ['PATH', '路径'],
|
|
512
|
-
'viewer.nav.radar': ['Open semantic radar', '打开语义雷达'],
|
|
513
|
-
'viewer.nav.radar.title': ['Semantic radar (M)', '语义雷达(M)'],
|
|
514
|
-
'viewer.nav.radar.short': ['MAP', '地图'],
|
|
515
|
-
'viewer.nav.lens': ['Open semantic lens', '打开语义透镜'],
|
|
516
|
-
'viewer.nav.lens.title': ['Semantic lens (L)', '语义透镜(L)'],
|
|
517
|
-
'viewer.nav.lens.short': ['LENS', '透镜'],
|
|
518
|
-
'viewer.nav.find': ['Find a node', '查找节点'],
|
|
519
|
-
'viewer.nav.find.title': ['Find a node (/)', '查找节点(/)'],
|
|
520
|
-
'viewer.nav.guide': ['Open diagram guide', '打开图表指南'],
|
|
521
|
-
'viewer.nav.guide.title': ['Diagram guide (?)', '图表指南(?)'],
|
|
522
|
-
'viewer.nav.zoomOut': ['Zoom out', '缩小'],
|
|
523
|
-
'viewer.nav.zoomOut.title': ['Zoom out (-)', '缩小(-)'],
|
|
524
|
-
'viewer.nav.reset': ['Reset diagram view', '重置图表视图'],
|
|
525
|
-
'viewer.nav.reset.title': ['Reset view (0)', '重置视图(0)'],
|
|
526
|
-
'viewer.nav.read': ['READ', '阅读'],
|
|
527
|
-
'viewer.nav.zoomIn': ['Zoom in', '放大'],
|
|
528
|
-
'viewer.nav.zoomIn.title': ['Zoom in (+)', '放大(+)'],
|
|
529
|
-
'viewer.nav.camera': ['{hint}. Reset diagram view', '{hint}。重置图表视图'],
|
|
530
|
-
'viewer.nav.camera.title': ['{semantic}{hint} · reset view (0)', '{semantic}{hint} · 重置视图(0)'],
|
|
531
|
-
'viewer.nav.camera.semantic': ['Semantic camera active · ', '语义相机已启用 · '],
|
|
532
|
-
'viewer.nav.level.map': ['MAP', '概览'],
|
|
533
|
-
'viewer.nav.level.read': ['READ', '阅读'],
|
|
534
|
-
'viewer.nav.level.full': ['FULL', '完整'],
|
|
535
|
-
'viewer.nav.level.auto': ['AUTO', '自动'],
|
|
536
|
-
'viewer.nav.detail.map': ['Zoom in to reveal relationship labels and node context', '放大以显示关系标签和节点上下文'],
|
|
537
|
-
'viewer.nav.detail.read': ['Zoom in again to reveal tags and annotations', '再次放大以显示标签和注释'],
|
|
538
|
-
'viewer.nav.detail.full': ['Full diagram detail', '完整图表详情'],
|
|
539
|
-
|
|
540
|
-
'viewer.intent.summary': ['{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.', '{label}。{out} 条出向,{in} 条入向{loops}。共 {total} 条连接。按 Enter 查看详情。'],
|
|
541
|
-
'viewer.intent.loops': [', {count} self loop', ',{count} 条自环'],
|
|
542
|
-
|
|
543
|
-
'viewer.common.copied': ['Copied', '已复制'],
|
|
544
|
-
'viewer.common.copyFailed': ['Copy failed', '复制失败'],
|
|
545
|
-
'viewer.common.copyLink': ['Copy link', '复制链接'],
|
|
546
|
-
'viewer.common.clear': ['Clear', '清除'],
|
|
547
|
-
'viewer.common.close': ['Close', '关闭'],
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
for (const [key, messages] of Object.entries(MESSAGE_PAIRS)) {
|
|
551
|
-
if (messages.length !== SUPPORTED_LOCALES.length || messages.some((message) => typeof message !== 'string')) {
|
|
552
|
-
throw new Error(`Incomplete i18n tuple ${JSON.stringify(key)}`);
|
|
12
|
+
// Bundled catalogs are package data, not renderer code. locales/manifest.json
|
|
13
|
+
// enrolls the catalogs meta.locale selects automatically; only listed files
|
|
14
|
+
// are read, relative to this installed package and never from the working
|
|
15
|
+
// directory, home, environment, or network. Adding a language changes data
|
|
16
|
+
// only. en.json defines the canonical keys and the final fallback.
|
|
17
|
+
const LOCALES_DIR = new URL('../../locales/', import.meta.url);
|
|
18
|
+
// Mirrors common.schema.json#/$defs/locale.
|
|
19
|
+
const LOCALE_TAG = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
|
|
20
|
+
const CATALOG_FILE = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
|
|
21
|
+
|
|
22
|
+
function readLocaleData(file) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(fileURLToPath(new URL(file, LOCALES_DIR)), 'utf8'));
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Archify locale data ${JSON.stringify(file)} could not be loaded: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function loadManifest() {
|
|
31
|
+
const catalogs = readLocaleData('manifest.json')?.catalogs;
|
|
32
|
+
if (!Array.isArray(catalogs)) throw new Error('Archify locale manifest must list catalogs');
|
|
33
|
+
const byKey = new Map();
|
|
34
|
+
for (const entry of catalogs) {
|
|
35
|
+
const { locale, file } = entry || {};
|
|
36
|
+
if (typeof locale !== 'string' || locale.length > 35 || !LOCALE_TAG.test(locale)) {
|
|
37
|
+
throw new Error(`Archify locale manifest has an invalid locale tag ${JSON.stringify(locale)}`);
|
|
38
|
+
}
|
|
39
|
+
if (typeof file !== 'string' || !CATALOG_FILE.test(file)) {
|
|
40
|
+
throw new Error(`Archify locale manifest has an invalid catalog file for ${locale}`);
|
|
41
|
+
}
|
|
42
|
+
// Tags match case-insensitively, so two entries differing only in case
|
|
43
|
+
// would make selection ambiguous.
|
|
44
|
+
const key = locale.toLowerCase();
|
|
45
|
+
if (byKey.has(key)) throw new Error(`Archify locale manifest lists ${locale} more than once`);
|
|
46
|
+
byKey.set(key, { locale, file });
|
|
47
|
+
}
|
|
48
|
+
if (byKey.get(DEFAULT_LOCALE)?.locale !== DEFAULT_LOCALE) {
|
|
49
|
+
throw new Error(`Archify locale manifest must enroll the ${DEFAULT_LOCALE} source catalog`);
|
|
50
|
+
}
|
|
51
|
+
return byKey;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MANIFEST = loadManifest();
|
|
55
|
+
|
|
56
|
+
export const SUPPORTED_LOCALES = Object.freeze([...MANIFEST.values()].map(({ locale }) => locale));
|
|
57
|
+
|
|
58
|
+
const EN = Object.freeze(readLocaleData(MANIFEST.get(DEFAULT_LOCALE).file));
|
|
59
|
+
for (const [key, message] of Object.entries(EN)) {
|
|
60
|
+
if (typeof message !== 'string' || message.length === 0) {
|
|
61
|
+
throw new Error(`Archify source message ${JSON.stringify(key)} must be a non-empty string`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const CANONICAL_KEYS = Object.keys(EN);
|
|
66
|
+
const PLACEHOLDER_PATTERN = /\{([a-zA-Z0-9_]+)\}/g;
|
|
67
|
+
|
|
68
|
+
function extractPlaceholders(message) {
|
|
69
|
+
return new Set([...String(message).matchAll(PLACEHOLDER_PATTERN)].map((match) => match[1]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const CANONICAL_PLACEHOLDERS = Object.fromEntries(
|
|
73
|
+
CANONICAL_KEYS.map((key) => [key, extractPlaceholders(EN[key])]),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
function placeholdersMatch(expected, actual) {
|
|
77
|
+
if (expected.size !== actual.size) return false;
|
|
78
|
+
for (const token of expected) if (!actual.has(token)) return false;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Splits translation data into the entries that can replace a canonical
|
|
83
|
+
// message and the ones that cannot. Rejected entries never replace a valid
|
|
84
|
+
// lower-priority message.
|
|
85
|
+
function classifyTranslations(translations) {
|
|
86
|
+
const supplied = Object.keys(translations || {});
|
|
87
|
+
const usable = new Map();
|
|
88
|
+
const unknownKeys = [];
|
|
89
|
+
const placeholderMismatches = [];
|
|
90
|
+
for (const key of supplied) {
|
|
91
|
+
if (!Object.hasOwn(CANONICAL_PLACEHOLDERS, key)) {
|
|
92
|
+
unknownKeys.push(key);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const value = translations[key];
|
|
96
|
+
const expected = [...CANONICAL_PLACEHOLDERS[key]].sort();
|
|
97
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
98
|
+
placeholderMismatches.push({ key, expected, actual: null });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const actual = extractPlaceholders(value);
|
|
102
|
+
if (placeholdersMatch(CANONICAL_PLACEHOLDERS[key], actual)) {
|
|
103
|
+
usable.set(key, value);
|
|
104
|
+
} else {
|
|
105
|
+
placeholderMismatches.push({ key, expected, actual: [...actual].sort() });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { supplied, usable, unknownKeys, placeholderMismatches };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validates caller-supplied translation data against the canonical (English)
|
|
112
|
+
// message-key set. Pure and side-effect free; coverage here describes only the
|
|
113
|
+
// supplied data. resolveCatalog() reports the final resolved coverage.
|
|
114
|
+
export function validateTranslations(translations = {}) {
|
|
115
|
+
const { usable, unknownKeys, placeholderMismatches } = classifyTranslations(translations);
|
|
116
|
+
return {
|
|
117
|
+
totalKeys: CANONICAL_KEYS.length,
|
|
118
|
+
coveredKeys: usable.size,
|
|
119
|
+
coverage: CANONICAL_KEYS.length ? usable.size / CANONICAL_KEYS.length : 1,
|
|
120
|
+
missingKeys: CANONICAL_KEYS.filter((key) => !Object.hasOwn(translations || {}, key)),
|
|
121
|
+
unknownKeys,
|
|
122
|
+
placeholderMismatches,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function bundledLocaleFor(locale) {
|
|
127
|
+
if (typeof locale !== 'string') return null;
|
|
128
|
+
return MANIFEST.get(locale.toLowerCase())?.locale || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isEnglishTag(locale) {
|
|
132
|
+
const key = locale.toLowerCase();
|
|
133
|
+
return key === DEFAULT_LOCALE || key.startsWith(`${DEFAULT_LOCALE}-`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Bundled catalogs load on first use and stay immutable; a diagram's
|
|
137
|
+
// overrides are layered onto a copy.
|
|
138
|
+
const BUNDLED_CATALOGS = new Map([[DEFAULT_LOCALE, { usable: new Map(Object.entries(EN)), messages: EN }]]);
|
|
139
|
+
|
|
140
|
+
function bundledCatalog(locale) {
|
|
141
|
+
if (!BUNDLED_CATALOGS.has(locale)) {
|
|
142
|
+
const { usable } = classifyTranslations(readLocaleData(MANIFEST.get(locale.toLowerCase()).file));
|
|
143
|
+
const messages = Object.freeze(Object.fromEntries(
|
|
144
|
+
CANONICAL_KEYS.map((key) => [key, usable.get(key) ?? EN[key]]),
|
|
145
|
+
));
|
|
146
|
+
BUNDLED_CATALOGS.set(locale, { usable, messages });
|
|
147
|
+
}
|
|
148
|
+
return BUNDLED_CATALOGS.get(locale);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Resolves each canonical message as: valid meta.translations value →
|
|
152
|
+
// selected bundled-catalog value → English source. Pure; returns the resolved
|
|
153
|
+
// catalog with a report that separates rejected overrides from real English
|
|
154
|
+
// fallback gaps. Omitted or empty translations mean no override. A tag with
|
|
155
|
+
// neither a bundled catalog nor a usable override falls back to English UI
|
|
156
|
+
// and language metadata.
|
|
157
|
+
export function resolveCatalog(locale, translations = undefined) {
|
|
158
|
+
const override = classifyTranslations(translations);
|
|
159
|
+
const bundledLocale = bundledLocaleFor(locale);
|
|
160
|
+
const fallback = Boolean(locale) && !bundledLocale && override.usable.size === 0;
|
|
161
|
+
const resolvedLocale = !locale || fallback ? DEFAULT_LOCALE : bundledLocale || locale;
|
|
162
|
+
const bundled = bundledLocale ? bundledCatalog(bundledLocale) : null;
|
|
163
|
+
const english = isEnglishTag(resolvedLocale);
|
|
164
|
+
const messages = {};
|
|
165
|
+
const fallbackKeys = [];
|
|
166
|
+
for (const key of CANONICAL_KEYS) {
|
|
167
|
+
if (override.usable.has(key)) {
|
|
168
|
+
messages[key] = override.usable.get(key);
|
|
169
|
+
} else if (bundled?.usable.has(key)) {
|
|
170
|
+
messages[key] = bundled.usable.get(key);
|
|
171
|
+
} else {
|
|
172
|
+
messages[key] = EN[key];
|
|
173
|
+
if (!english) fallbackKeys.push(key);
|
|
174
|
+
}
|
|
553
175
|
}
|
|
176
|
+
const translatedKeys = english ? CANONICAL_KEYS.length : CANONICAL_KEYS.length - fallbackKeys.length;
|
|
177
|
+
return {
|
|
178
|
+
messages: Object.freeze(messages),
|
|
179
|
+
report: {
|
|
180
|
+
locale: locale || null,
|
|
181
|
+
resolvedLocale,
|
|
182
|
+
bundledLocale,
|
|
183
|
+
fallback,
|
|
184
|
+
totalKeys: CANONICAL_KEYS.length,
|
|
185
|
+
translatedKeys,
|
|
186
|
+
coverage: CANONICAL_KEYS.length ? translatedKeys / CANONICAL_KEYS.length : 1,
|
|
187
|
+
fallbackKeys,
|
|
188
|
+
override: {
|
|
189
|
+
suppliedKeys: override.supplied.length,
|
|
190
|
+
appliedKeys: override.usable.size,
|
|
191
|
+
unknownKeys: override.unknownKeys,
|
|
192
|
+
placeholderMismatches: override.placeholderMismatches,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
};
|
|
554
196
|
}
|
|
555
197
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
198
|
+
// Per-document catalogs installed by registerLocale(). Tags are
|
|
199
|
+
// case-insensitive, so the key is lowercased: renderers may pass the authored
|
|
200
|
+
// meta.locale (zh-cn) or its resolved tag (zh-CN) and reach the same document
|
|
201
|
+
// catalog. They never mutate a bundled catalog.
|
|
202
|
+
const DOCUMENT_CATALOGS = new Map();
|
|
203
|
+
|
|
204
|
+
function documentKey(locale) {
|
|
205
|
+
return typeof locale === 'string' ? locale.toLowerCase() : locale;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function catalogFor(locale) {
|
|
209
|
+
const key = documentKey(locale);
|
|
210
|
+
if (DOCUMENT_CATALOGS.has(key)) return DOCUMENT_CATALOGS.get(key);
|
|
211
|
+
const bundledLocale = bundledLocaleFor(locale);
|
|
212
|
+
return bundledLocale ? { locale: bundledLocale, messages: bundledCatalog(bundledLocale).messages } : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Installs the resolved catalog for one authored locale and returns its
|
|
216
|
+
// report. Re-registering the same tag replaces the previous document's
|
|
217
|
+
// overrides; a fallback clears them.
|
|
218
|
+
export function registerLocale(locale, translations = undefined) {
|
|
219
|
+
const { messages, report } = resolveCatalog(locale, translations);
|
|
220
|
+
if (report.fallback || !locale) {
|
|
221
|
+
DOCUMENT_CATALOGS.delete(documentKey(locale));
|
|
222
|
+
} else {
|
|
223
|
+
DOCUMENT_CATALOGS.set(documentKey(locale), { locale: report.resolvedLocale, messages });
|
|
224
|
+
}
|
|
225
|
+
return report;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Agent-facing warnings for one document's meta.locale/meta.translations.
|
|
229
|
+
// Pure, so the renderer and the CLI receipts (validate/deliver/finalize)
|
|
230
|
+
// report the same diagnostics from the same input.
|
|
231
|
+
export function localeDiagnostics(diagramType, meta) {
|
|
232
|
+
const locale = meta?.locale;
|
|
233
|
+
if (!locale) return [];
|
|
234
|
+
const report = resolveCatalog(locale, meta.translations).report;
|
|
235
|
+
const diagnostics = [];
|
|
236
|
+
const { unknownKeys, placeholderMismatches, appliedKeys } = report.override;
|
|
237
|
+
const rejected = unknownKeys.length + placeholderMismatches.length;
|
|
238
|
+
if (rejected) {
|
|
239
|
+
const kept = report.bundledLocale ? `bundled ${report.bundledLocale} message` : 'English message';
|
|
240
|
+
diagnostics.push({
|
|
241
|
+
code: 'i18n/invalid-translation',
|
|
242
|
+
severity: 'warning',
|
|
243
|
+
message: `meta.translations for locale ${JSON.stringify(locale)} has ${rejected} unusable ${rejected === 1 ? 'entry' : 'entries'} (${unknownKeys.length} unknown, ${placeholderMismatches.length} placeholder mismatch); each keeps its ${kept}.`,
|
|
244
|
+
subject: { diagramType, path: '/meta/translations' },
|
|
245
|
+
evidence: {
|
|
246
|
+
locale,
|
|
247
|
+
unknownKeys: unknownKeys.slice(0, 10),
|
|
248
|
+
unknownKeysTotal: unknownKeys.length,
|
|
249
|
+
placeholderMismatches: placeholderMismatches.slice(0, 10),
|
|
250
|
+
placeholderMismatchesTotal: placeholderMismatches.length,
|
|
251
|
+
},
|
|
252
|
+
supportedFixes: ['Remove each unknown key or rename it to a canonical message key.', 'Use exactly the {placeholders} of the English source string for each mismatched key.'],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (report.fallback) {
|
|
256
|
+
diagnostics.push({
|
|
257
|
+
code: 'i18n/locale-fallback',
|
|
258
|
+
severity: 'warning',
|
|
259
|
+
message: `meta.locale ${JSON.stringify(locale)} has no bundled catalog and no usable meta.translations; the Viewer chrome and <html lang> fall back to English.`,
|
|
260
|
+
subject: { diagramType, path: '/meta/locale' },
|
|
261
|
+
evidence: { locale, bundledLocales: SUPPORTED_LOCALES },
|
|
262
|
+
supportedFixes: ['Supply meta.translations for this locale.', `Use a bundled locale: ${SUPPORTED_LOCALES.join(', ')}.`],
|
|
263
|
+
});
|
|
264
|
+
} else if (report.fallbackKeys.length) {
|
|
265
|
+
const source = [
|
|
266
|
+
report.bundledLocale && `the bundled ${report.bundledLocale} catalog`,
|
|
267
|
+
appliedKeys && `${appliedKeys} meta.translations ${appliedKeys === 1 ? 'entry' : 'entries'}`,
|
|
268
|
+
].filter(Boolean).join(' and ');
|
|
269
|
+
const missingKeys = report.fallbackKeys.slice(0, 10);
|
|
270
|
+
diagnostics.push({
|
|
271
|
+
code: 'i18n/translation-coverage',
|
|
272
|
+
severity: 'warning',
|
|
273
|
+
message: `meta.locale ${JSON.stringify(locale)} resolves ${report.translatedKeys}/${report.totalKeys} renderer-owned messages (${Math.floor(report.coverage * 100)}%) from ${source}; ${report.fallbackKeys.length} fall back to English.`,
|
|
274
|
+
subject: { diagramType, path: '/meta/translations' },
|
|
275
|
+
evidence: {
|
|
276
|
+
locale,
|
|
277
|
+
resolvedLocale: report.resolvedLocale,
|
|
278
|
+
bundledLocale: report.bundledLocale,
|
|
279
|
+
translatedKeys: report.translatedKeys,
|
|
280
|
+
totalKeys: report.totalKeys,
|
|
281
|
+
appliedOverrideKeys: appliedKeys,
|
|
282
|
+
missingKeys,
|
|
283
|
+
missingKeysTotal: report.fallbackKeys.length,
|
|
284
|
+
// English source for the listed gaps, so a caller can translate
|
|
285
|
+
// exactly those keys without re-reading the whole catalog.
|
|
286
|
+
englishSource: Object.fromEntries(missingKeys.map((key) => [key, EN[key]])),
|
|
287
|
+
},
|
|
288
|
+
supportedFixes: ['Add each missing key to meta.translations, translating its English source string and keeping its {placeholders}.'],
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return diagnostics;
|
|
292
|
+
}
|
|
560
293
|
|
|
561
294
|
export function resolveLocale(locale) {
|
|
562
|
-
return
|
|
295
|
+
return catalogFor(locale)?.locale || DEFAULT_LOCALE;
|
|
563
296
|
}
|
|
564
297
|
|
|
565
298
|
export function formatMessage(template, values = {}) {
|
|
@@ -569,11 +302,11 @@ export function formatMessage(template, values = {}) {
|
|
|
569
302
|
}
|
|
570
303
|
|
|
571
304
|
export function translateMessage(locale, key, values = {}) {
|
|
572
|
-
const
|
|
573
|
-
if (!Object.hasOwn(
|
|
574
|
-
throw new Error(`Missing i18n message ${JSON.stringify(key)} for ${
|
|
305
|
+
const catalog = catalogFor(locale)?.messages || EN;
|
|
306
|
+
if (!Object.hasOwn(catalog, key)) {
|
|
307
|
+
throw new Error(`Missing Archify i18n message ${JSON.stringify(key)} for ${resolveLocale(locale)}`);
|
|
575
308
|
}
|
|
576
|
-
return formatMessage(
|
|
309
|
+
return formatMessage(catalog[key], values);
|
|
577
310
|
}
|
|
578
311
|
|
|
579
312
|
export function translateCount(locale, key, count, values = {}) {
|
|
@@ -582,8 +315,8 @@ export function translateCount(locale, key, count, values = {}) {
|
|
|
582
315
|
}
|
|
583
316
|
|
|
584
317
|
export function viewerCatalog(locale) {
|
|
585
|
-
const
|
|
586
|
-
return Object.fromEntries(Object.entries(
|
|
318
|
+
const messages = catalogFor(locale)?.messages || EN;
|
|
319
|
+
return Object.fromEntries(Object.entries(messages).filter(([key]) => key.startsWith('viewer.')));
|
|
587
320
|
}
|
|
588
321
|
|
|
589
322
|
export function localizeTemplate(template, locale) {
|
|
@@ -591,5 +324,5 @@ export function localizeTemplate(template, locale) {
|
|
|
591
324
|
}
|
|
592
325
|
|
|
593
326
|
export function catalogKeys() {
|
|
594
|
-
return
|
|
327
|
+
return [...CANONICAL_KEYS];
|
|
595
328
|
}
|