@mandolin/jsdoc-plugin-hia-sys 0.1.0
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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/RELEASE_CHECKLIST.md +41 -0
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/examples/basic/README.md +22 -0
- package/examples/basic/i18n/docs.hia-i18n.json +12 -0
- package/examples/basic/jsdoc.conf.json +42 -0
- package/examples/basic/src/greet.js +26 -0
- package/examples/basic/src/shared.js +20 -0
- package/package.json +61 -0
- package/src/config/defaults.cjs +114 -0
- package/src/index.cjs +37 -0
- package/src/micro-plugins/code-fragment.cjs +374 -0
- package/src/micro-plugins/diagnostics.cjs +18 -0
- package/src/micro-plugins/doc-i18n.cjs +796 -0
- package/src/micro-plugins/doclet-normalizer.cjs +23 -0
- package/src/micro-plugins/hia-ir-exporter.cjs +68 -0
- package/src/micro-plugins/index.cjs +23 -0
- package/src/micro-plugins/source-link.cjs +19 -0
- package/src/micro-plugins/source-preview.cjs +18 -0
- package/src/runtime/create-plugin-system.cjs +120 -0
- package/src/runtime/diagnostics.cjs +45 -0
- package/src/runtime/i18n.cjs +221 -0
- package/src/runtime/metadata.cjs +52 -0
- package/src/runtime/output-contract.cjs +416 -0
- package/src/runtime/source.cjs +547 -0
- package/src/version.cjs +5 -0
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getResourceEntry,
|
|
5
|
+
loadLocalizationResources,
|
|
6
|
+
normalizeLocalizedValue,
|
|
7
|
+
parseLocaleValue
|
|
8
|
+
} = require("../runtime/i18n.cjs");
|
|
9
|
+
|
|
10
|
+
const TEXT_I18N_MODEL = "hia-jsdoc-text-i18n";
|
|
11
|
+
const TEXT_I18N_MODEL_VERSION = "0.2.0";
|
|
12
|
+
|
|
13
|
+
function rememberTag(doclet, name, value) {
|
|
14
|
+
doclet.hiaTags = doclet.hiaTags || {};
|
|
15
|
+
doclet.hiaTags[name] = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function appendTag(doclet, name, value) {
|
|
19
|
+
doclet.hiaTags = doclet.hiaTags || {};
|
|
20
|
+
doclet.hiaTags[name] = doclet.hiaTags[name] || [];
|
|
21
|
+
doclet.hiaTags[name].push(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getDocletLabel(doclet) {
|
|
25
|
+
return doclet.longname || doclet.name || "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getFallbackLocales(config) {
|
|
29
|
+
const fallbackLocale = config.i18n.fallbackLocale;
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(fallbackLocale)) {
|
|
32
|
+
return fallbackLocale.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return fallbackLocale ? [fallbackLocale] : [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeLocales(config) {
|
|
39
|
+
const locales = Array.isArray(config.i18n.locales)
|
|
40
|
+
? config.i18n.locales.slice()
|
|
41
|
+
: [];
|
|
42
|
+
|
|
43
|
+
if (config.i18n.defaultLocale && !locales.includes(config.i18n.defaultLocale)) {
|
|
44
|
+
locales.unshift(config.i18n.defaultLocale);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return locales;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function pushUnique(target, value) {
|
|
51
|
+
if (value && !target.includes(value)) {
|
|
52
|
+
target.push(value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getParentLocale(locale) {
|
|
57
|
+
const match = /^([A-Za-z]{2,3})[-_]/.exec(locale || "");
|
|
58
|
+
return match ? match[1] : "";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildFallbackChain(requestedLocale, config) {
|
|
62
|
+
const chain = [];
|
|
63
|
+
|
|
64
|
+
pushUnique(chain, requestedLocale);
|
|
65
|
+
pushUnique(chain, getParentLocale(requestedLocale));
|
|
66
|
+
|
|
67
|
+
for (const locale of getFallbackLocales(config)) {
|
|
68
|
+
pushUnique(chain, locale);
|
|
69
|
+
pushUnique(chain, getParentLocale(locale));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pushUnique(chain, config.i18n.defaultLocale);
|
|
73
|
+
|
|
74
|
+
return chain;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isKnownLocale(locale, context) {
|
|
78
|
+
const knownLocales = new Set([
|
|
79
|
+
...normalizeLocales(context.config),
|
|
80
|
+
...getFallbackLocales(context.config),
|
|
81
|
+
context.config.i18n.defaultLocale
|
|
82
|
+
].filter(Boolean));
|
|
83
|
+
|
|
84
|
+
return knownLocales.has(locale) || knownLocales.has(getParentLocale(locale));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function addUnknownLocaleDiagnostic(locale, context, doclet, fieldPath) {
|
|
88
|
+
if (!locale || isKnownLocale(locale, context)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
context.addDiagnostic({
|
|
93
|
+
code: "HIA_I18N_LOCALE_UNKNOWN",
|
|
94
|
+
severity: "warning",
|
|
95
|
+
message: `Unknown locale in localized documentation: ${locale}`,
|
|
96
|
+
plugin: "doc-i18n",
|
|
97
|
+
data: {
|
|
98
|
+
locale,
|
|
99
|
+
fieldPath,
|
|
100
|
+
doclet: getDocletLabel(doclet)
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildLocalizationKey(doclet, tags) {
|
|
106
|
+
return tags.key || doclet.longname || doclet.name || "";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildLocalizationPath(doclet, tags) {
|
|
110
|
+
return tags.path || doclet.memberof || doclet.longname || doclet.name || "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseAttributes(rawAttributes) {
|
|
114
|
+
const attributes = {};
|
|
115
|
+
const pattern = /([A-Za-z_:][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
116
|
+
let match;
|
|
117
|
+
|
|
118
|
+
while ((match = pattern.exec(rawAttributes || ""))) {
|
|
119
|
+
attributes[match[1]] = match[2] || match[3] || "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return attributes;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseInlineLocalizedValues(innerText, context, doclet, fieldPath) {
|
|
126
|
+
const localized = {};
|
|
127
|
+
const pattern = /<([A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8})*)>([\s\S]*?)<\/\1>/g;
|
|
128
|
+
let match;
|
|
129
|
+
|
|
130
|
+
while ((match = pattern.exec(innerText || ""))) {
|
|
131
|
+
const locale = match[1];
|
|
132
|
+
|
|
133
|
+
addUnknownLocaleDiagnostic(locale, context, doclet, fieldPath);
|
|
134
|
+
|
|
135
|
+
if (Object.prototype.hasOwnProperty.call(localized, locale)) {
|
|
136
|
+
context.addDiagnostic({
|
|
137
|
+
code: "HIA_I18N_INLINE_LOCALE_DUPLICATE",
|
|
138
|
+
severity: "error",
|
|
139
|
+
message: `Duplicate inline localized text for locale: ${locale}`,
|
|
140
|
+
plugin: "doc-i18n",
|
|
141
|
+
data: {
|
|
142
|
+
locale,
|
|
143
|
+
fieldPath,
|
|
144
|
+
doclet: getDocletLabel(doclet)
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
localized[locale] = match[2].trim();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return localized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseInlineSegments(text, fieldPath, context, doclet) {
|
|
156
|
+
const sourceText = String(text || "");
|
|
157
|
+
const segments = [];
|
|
158
|
+
const seenKeys = new Set();
|
|
159
|
+
const pattern = /<lang\b([^>]*)>([\s\S]*?)<\/lang>/g;
|
|
160
|
+
let match;
|
|
161
|
+
|
|
162
|
+
while ((match = pattern.exec(sourceText))) {
|
|
163
|
+
const attributes = parseAttributes(match[1]);
|
|
164
|
+
const localized = parseInlineLocalizedValues(match[2], context, doclet, fieldPath);
|
|
165
|
+
const key = attributes.key || "";
|
|
166
|
+
|
|
167
|
+
if (key) {
|
|
168
|
+
if (seenKeys.has(key)) {
|
|
169
|
+
context.addDiagnostic({
|
|
170
|
+
code: "HIA_I18N_INLINE_KEY_DUPLICATE",
|
|
171
|
+
severity: "warning",
|
|
172
|
+
message: `Duplicate inline localized key: ${key}`,
|
|
173
|
+
plugin: "doc-i18n",
|
|
174
|
+
data: {
|
|
175
|
+
key,
|
|
176
|
+
fieldPath,
|
|
177
|
+
doclet: getDocletLabel(doclet)
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
seenKeys.add(key);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (Object.keys(localized).length === 0) {
|
|
186
|
+
context.addDiagnostic({
|
|
187
|
+
code: "HIA_I18N_INLINE_LANG_MALFORMED",
|
|
188
|
+
severity: "error",
|
|
189
|
+
message: "Malformed <lang> inline segment has no localized child nodes.",
|
|
190
|
+
plugin: "doc-i18n",
|
|
191
|
+
data: {
|
|
192
|
+
fieldPath,
|
|
193
|
+
doclet: getDocletLabel(doclet)
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
segments.push({
|
|
199
|
+
kind: "lang-inline",
|
|
200
|
+
id: `${fieldPath}.${segments.length}`,
|
|
201
|
+
key,
|
|
202
|
+
path: attributes.path || "",
|
|
203
|
+
fieldPath,
|
|
204
|
+
raw: match[0],
|
|
205
|
+
localized,
|
|
206
|
+
rangeInField: {
|
|
207
|
+
start: match.index,
|
|
208
|
+
end: match.index + match[0].length
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const rawLangCount = (sourceText.match(/<lang\b/gi) || []).length;
|
|
214
|
+
|
|
215
|
+
if (rawLangCount > segments.length) {
|
|
216
|
+
context.addDiagnostic({
|
|
217
|
+
code: "HIA_I18N_INLINE_LANG_MALFORMED",
|
|
218
|
+
severity: "error",
|
|
219
|
+
message: "Malformed <lang> inline segment was found.",
|
|
220
|
+
plugin: "doc-i18n",
|
|
221
|
+
data: {
|
|
222
|
+
fieldPath,
|
|
223
|
+
doclet: getDocletLabel(doclet)
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return segments;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function resolveLocalizedValue(variants, requestedLocale, config, defaultText) {
|
|
232
|
+
const fallbackChain = buildFallbackChain(requestedLocale, config);
|
|
233
|
+
|
|
234
|
+
for (const locale of fallbackChain) {
|
|
235
|
+
if (typeof variants[locale] === "string" && variants[locale].length > 0) {
|
|
236
|
+
return {
|
|
237
|
+
text: variants[locale],
|
|
238
|
+
resolution: {
|
|
239
|
+
requestedLocale,
|
|
240
|
+
resolvedLocale: locale,
|
|
241
|
+
fallbackChain,
|
|
242
|
+
usedFallback: locale !== requestedLocale,
|
|
243
|
+
missing: false
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
text: defaultText || "",
|
|
251
|
+
resolution: {
|
|
252
|
+
requestedLocale,
|
|
253
|
+
resolvedLocale: defaultText ? config.i18n.defaultLocale : "",
|
|
254
|
+
fallbackChain,
|
|
255
|
+
usedFallback: Boolean(defaultText && requestedLocale !== config.i18n.defaultLocale),
|
|
256
|
+
missing: !defaultText
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function renderTextWithSegments(text, segments, requestedLocale, config) {
|
|
262
|
+
let result = "";
|
|
263
|
+
let lastIndex = 0;
|
|
264
|
+
|
|
265
|
+
for (const segment of segments) {
|
|
266
|
+
const resolved = resolveLocalizedValue(
|
|
267
|
+
segment.localized,
|
|
268
|
+
requestedLocale,
|
|
269
|
+
config,
|
|
270
|
+
segment.raw
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
result += String(text || "").slice(lastIndex, segment.rangeInField.start);
|
|
274
|
+
result += resolved.text;
|
|
275
|
+
lastIndex = segment.rangeInField.end;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
result += String(text || "").slice(lastIndex);
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function createLangBlock(rawValue, source, context, doclet) {
|
|
284
|
+
const parsed = parseLocaleValue(rawValue);
|
|
285
|
+
const locale = parsed.locale;
|
|
286
|
+
|
|
287
|
+
if (!locale) {
|
|
288
|
+
context.addDiagnostic({
|
|
289
|
+
code: source.startsWith("legacy")
|
|
290
|
+
? "HIA_I18N_INLINE_LOCALE_MISSING"
|
|
291
|
+
: "HIA_I18N_LANG_LOCALE_MISSING",
|
|
292
|
+
severity: "error",
|
|
293
|
+
message: "Missing locale for localized documentation block.",
|
|
294
|
+
plugin: "doc-i18n",
|
|
295
|
+
data: {
|
|
296
|
+
doclet: getDocletLabel(doclet)
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (locale) {
|
|
302
|
+
addUnknownLocaleDiagnostic(locale, context, doclet, "description");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (locale && !parsed.value) {
|
|
306
|
+
context.addDiagnostic({
|
|
307
|
+
code: "HIA_I18N_LANG_EMPTY",
|
|
308
|
+
severity: "warning",
|
|
309
|
+
message: `Empty localized documentation block for locale: ${locale}`,
|
|
310
|
+
plugin: "doc-i18n",
|
|
311
|
+
data: {
|
|
312
|
+
locale,
|
|
313
|
+
doclet: getDocletLabel(doclet)
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
kind: "lang-block",
|
|
320
|
+
locale,
|
|
321
|
+
fieldPath: "description",
|
|
322
|
+
text: parsed.value,
|
|
323
|
+
block: "",
|
|
324
|
+
source,
|
|
325
|
+
rangeInComment: null
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function addBlock(blocks, block, context, doclet) {
|
|
330
|
+
if (!block.locale) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const duplicate = blocks.some((item) => item.locale === block.locale);
|
|
335
|
+
|
|
336
|
+
if (duplicate) {
|
|
337
|
+
context.addDiagnostic({
|
|
338
|
+
code: block.source.startsWith("legacy")
|
|
339
|
+
? "HIA_I18N_INLINE_LOCALE_DUPLICATE"
|
|
340
|
+
: "HIA_I18N_LANG_LOCALE_DUPLICATE",
|
|
341
|
+
severity: "error",
|
|
342
|
+
message: `Duplicate localized documentation block for locale: ${block.locale}`,
|
|
343
|
+
plugin: "doc-i18n",
|
|
344
|
+
data: {
|
|
345
|
+
locale: block.locale,
|
|
346
|
+
fieldPath: block.fieldPath,
|
|
347
|
+
doclet: getDocletLabel(doclet)
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
blocks.push(block);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function buildDescriptionBlocks(doclet, tags, context) {
|
|
356
|
+
const blocks = [];
|
|
357
|
+
|
|
358
|
+
for (const rawValue of tags.lang || []) {
|
|
359
|
+
addBlock(blocks, createLangBlock(rawValue, "tag", context, doclet), context, doclet);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const rawValue of tags.text || []) {
|
|
363
|
+
addBlock(blocks, createLangBlock(rawValue, "legacy-text", context, doclet), context, doclet);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const rawValue of tags.block || []) {
|
|
367
|
+
const block = createLangBlock(rawValue, "legacy-block", context, doclet);
|
|
368
|
+
block.block = block.text;
|
|
369
|
+
addBlock(blocks, block, context, doclet);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return blocks;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function addResourceBlocks(blocks, key, context) {
|
|
376
|
+
const existingLocales = new Set(blocks.map((block) => block.locale));
|
|
377
|
+
|
|
378
|
+
for (const locale of normalizeLocales(context.config)) {
|
|
379
|
+
if (existingLocales.has(locale)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const resourceEntry = getResourceEntry(
|
|
384
|
+
context.state.registries.localizationEntries,
|
|
385
|
+
locale,
|
|
386
|
+
key
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
if (!resourceEntry) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const normalized = normalizeLocalizedValue(resourceEntry);
|
|
394
|
+
blocks.push({
|
|
395
|
+
kind: "lang-block",
|
|
396
|
+
locale,
|
|
397
|
+
fieldPath: "description",
|
|
398
|
+
text: normalized.text || normalized.block || "",
|
|
399
|
+
block: normalized.block || "",
|
|
400
|
+
source: "resource",
|
|
401
|
+
rangeInComment: null,
|
|
402
|
+
data: normalized.data
|
|
403
|
+
});
|
|
404
|
+
existingLocales.add(locale);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function sanitizeFieldPathPart(value, fallback) {
|
|
409
|
+
const text = String(value || fallback || "").trim();
|
|
410
|
+
return text.replace(/\s+/g, "_").replace(/[.]/g, "_") || fallback;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function addTextField(target, fieldPath, kind, defaultText, source, blocks) {
|
|
414
|
+
if (!defaultText && (!Array.isArray(blocks) || blocks.length === 0)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
target.push({
|
|
419
|
+
fieldPath,
|
|
420
|
+
kind,
|
|
421
|
+
defaultText: String(defaultText || ""),
|
|
422
|
+
source,
|
|
423
|
+
blocks: Array.isArray(blocks) ? blocks : []
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function splitExample(example) {
|
|
428
|
+
const text = String(example || "");
|
|
429
|
+
const match = /^<caption>([\s\S]*?)<\/caption>\s*([\s\S]*)$/i.exec(text);
|
|
430
|
+
|
|
431
|
+
if (!match) {
|
|
432
|
+
return {
|
|
433
|
+
caption: "",
|
|
434
|
+
body: text
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
caption: match[1].trim(),
|
|
440
|
+
body: match[2]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function collectTextFields(doclet, descriptionBlocks) {
|
|
445
|
+
const fields = [];
|
|
446
|
+
|
|
447
|
+
addTextField(fields, "description", "description", doclet.description, "doclet.description", descriptionBlocks);
|
|
448
|
+
addTextField(fields, "classdesc", "classdesc", doclet.classdesc, "doclet.classdesc", []);
|
|
449
|
+
|
|
450
|
+
for (const [index, param] of (doclet.params || []).entries()) {
|
|
451
|
+
addTextField(
|
|
452
|
+
fields,
|
|
453
|
+
`params.${sanitizeFieldPathPart(param.name, index)}.description`,
|
|
454
|
+
"param.description",
|
|
455
|
+
param.description,
|
|
456
|
+
`doclet.params.${index}.description`,
|
|
457
|
+
[]
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
for (const [index, item] of (doclet.returns || []).entries()) {
|
|
462
|
+
addTextField(
|
|
463
|
+
fields,
|
|
464
|
+
`returns.${index}.description`,
|
|
465
|
+
"return.description",
|
|
466
|
+
item.description,
|
|
467
|
+
`doclet.returns.${index}.description`,
|
|
468
|
+
[]
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const [index, property] of (doclet.properties || []).entries()) {
|
|
473
|
+
addTextField(
|
|
474
|
+
fields,
|
|
475
|
+
`properties.${sanitizeFieldPathPart(property.name, index)}.description`,
|
|
476
|
+
"property.description",
|
|
477
|
+
property.description,
|
|
478
|
+
`doclet.properties.${index}.description`,
|
|
479
|
+
[]
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (const [index, example] of (doclet.examples || []).entries()) {
|
|
484
|
+
const parsed = splitExample(example);
|
|
485
|
+
addTextField(
|
|
486
|
+
fields,
|
|
487
|
+
`examples.${index}.caption`,
|
|
488
|
+
"example.caption",
|
|
489
|
+
parsed.caption,
|
|
490
|
+
`doclet.examples.${index}.caption`,
|
|
491
|
+
[]
|
|
492
|
+
);
|
|
493
|
+
addTextField(
|
|
494
|
+
fields,
|
|
495
|
+
`examples.${index}.body`,
|
|
496
|
+
"example.body",
|
|
497
|
+
parsed.body,
|
|
498
|
+
`doclet.examples.${index}.body`,
|
|
499
|
+
[]
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return fields;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function findBlockForLocale(blocks, locale) {
|
|
507
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
508
|
+
if (blocks[index].locale === locale) {
|
|
509
|
+
return blocks[index];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function resolveFieldText(fieldSeed, segments, requestedLocale, context) {
|
|
517
|
+
const fallbackChain = buildFallbackChain(requestedLocale, context.config);
|
|
518
|
+
|
|
519
|
+
for (const locale of fallbackChain) {
|
|
520
|
+
const block = findBlockForLocale(fieldSeed.blocks, locale);
|
|
521
|
+
|
|
522
|
+
if (block && (block.text || block.block)) {
|
|
523
|
+
return {
|
|
524
|
+
text: block.text || block.block,
|
|
525
|
+
resolution: {
|
|
526
|
+
requestedLocale,
|
|
527
|
+
resolvedLocale: locale,
|
|
528
|
+
fallbackChain,
|
|
529
|
+
usedFallback: locale !== requestedLocale,
|
|
530
|
+
missing: false
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const rendered = renderTextWithSegments(
|
|
537
|
+
fieldSeed.defaultText,
|
|
538
|
+
segments,
|
|
539
|
+
requestedLocale,
|
|
540
|
+
context.config
|
|
541
|
+
);
|
|
542
|
+
const hasText = Boolean(rendered);
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
text: rendered,
|
|
546
|
+
resolution: {
|
|
547
|
+
requestedLocale,
|
|
548
|
+
resolvedLocale: hasText ? context.config.i18n.defaultLocale : "",
|
|
549
|
+
fallbackChain,
|
|
550
|
+
usedFallback: hasText && requestedLocale !== context.config.i18n.defaultLocale,
|
|
551
|
+
missing: !hasText
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function createTextField(fieldSeed, context, doclet) {
|
|
557
|
+
const segments = parseInlineSegments(
|
|
558
|
+
fieldSeed.defaultText,
|
|
559
|
+
fieldSeed.fieldPath,
|
|
560
|
+
context,
|
|
561
|
+
doclet
|
|
562
|
+
);
|
|
563
|
+
const localizedText = {};
|
|
564
|
+
const resolutions = {};
|
|
565
|
+
const directLocales = new Set();
|
|
566
|
+
const locales = normalizeLocales(context.config);
|
|
567
|
+
|
|
568
|
+
if (fieldSeed.defaultText) {
|
|
569
|
+
directLocales.add(context.config.i18n.defaultLocale);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
for (const block of fieldSeed.blocks) {
|
|
573
|
+
if (block.locale) {
|
|
574
|
+
directLocales.add(block.locale);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
for (const locale of locales) {
|
|
579
|
+
const resolved = resolveFieldText(fieldSeed, segments, locale, context);
|
|
580
|
+
localizedText[locale] = resolved.text;
|
|
581
|
+
resolutions[locale] = resolved.resolution;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return {
|
|
585
|
+
fieldPath: fieldSeed.fieldPath,
|
|
586
|
+
kind: fieldSeed.kind,
|
|
587
|
+
defaultLocale: context.config.i18n.defaultLocale,
|
|
588
|
+
defaultText: fieldSeed.defaultText,
|
|
589
|
+
source: fieldSeed.source,
|
|
590
|
+
blocks: fieldSeed.blocks,
|
|
591
|
+
segments,
|
|
592
|
+
localizedText,
|
|
593
|
+
resolutions,
|
|
594
|
+
missingLocales: locales.filter((locale) => !directLocales.has(locale))
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function buildFields(doclet, descriptionBlocks, context) {
|
|
599
|
+
const fields = {};
|
|
600
|
+
|
|
601
|
+
for (const fieldSeed of collectTextFields(doclet, descriptionBlocks)) {
|
|
602
|
+
fields[fieldSeed.fieldPath] = createTextField(fieldSeed, context, doclet);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return fields;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function hasLocalizationIntent(tags) {
|
|
609
|
+
return Boolean(
|
|
610
|
+
tags.key ||
|
|
611
|
+
tags.path ||
|
|
612
|
+
tags.locale ||
|
|
613
|
+
(Array.isArray(tags.lang) && tags.lang.length > 0) ||
|
|
614
|
+
(Array.isArray(tags.text) && tags.text.length > 0) ||
|
|
615
|
+
(Array.isArray(tags.block) && tags.block.length > 0)
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function diagnoseMissingDescriptionLocales(descriptionField, context, doclet, tags) {
|
|
620
|
+
if (!descriptionField || !hasLocalizationIntent(tags)) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for (const locale of descriptionField.missingLocales) {
|
|
625
|
+
context.addDiagnostic({
|
|
626
|
+
code: "HIA_I18N_LOCALE_MISSING",
|
|
627
|
+
severity: "warning",
|
|
628
|
+
message: `Missing localized content for locale: ${locale}`,
|
|
629
|
+
plugin: "doc-i18n",
|
|
630
|
+
data: {
|
|
631
|
+
locale,
|
|
632
|
+
fieldPath: "description",
|
|
633
|
+
doclet: getDocletLabel(doclet)
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function getCompatSource(field, locale) {
|
|
640
|
+
const block = findBlockForLocale(field.blocks, locale);
|
|
641
|
+
|
|
642
|
+
if (!block) {
|
|
643
|
+
return locale === field.defaultLocale ? "description" : "fallback";
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (block.source.startsWith("legacy")) {
|
|
647
|
+
return "inline";
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return block.source;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function buildCompatLocalized(descriptionField, context) {
|
|
654
|
+
const localized = {};
|
|
655
|
+
|
|
656
|
+
if (!descriptionField) {
|
|
657
|
+
return localized;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
for (const locale of normalizeLocales(context.config)) {
|
|
661
|
+
const block = findBlockForLocale(descriptionField.blocks, locale);
|
|
662
|
+
localized[locale] = {
|
|
663
|
+
locale,
|
|
664
|
+
text: descriptionField.localizedText[locale] || "",
|
|
665
|
+
block: block && block.block ? block.block : "",
|
|
666
|
+
source: getCompatSource(descriptionField, locale)
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return localized;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function buildGenerationData(localized, fields, context) {
|
|
674
|
+
const locales = normalizeLocales(context.config);
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
mode: context.config.i18n.mode,
|
|
678
|
+
perLocale: locales.reduce((result, locale) => {
|
|
679
|
+
result[locale] = localized[locale] || null;
|
|
680
|
+
return result;
|
|
681
|
+
}, {}),
|
|
682
|
+
runtimeSwitch: {
|
|
683
|
+
locales,
|
|
684
|
+
fallbackLocale: context.config.i18n.fallbackLocale,
|
|
685
|
+
content: localized,
|
|
686
|
+
fields
|
|
687
|
+
},
|
|
688
|
+
hiaIntegration: {
|
|
689
|
+
localized,
|
|
690
|
+
fields
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
module.exports = {
|
|
696
|
+
name: "doc-i18n",
|
|
697
|
+
order: 40,
|
|
698
|
+
|
|
699
|
+
setup(context) {
|
|
700
|
+
const loaded = loadLocalizationResources(context.config, context.diagnostics);
|
|
701
|
+
context.state.registries.localizationResources = loaded.resources;
|
|
702
|
+
context.state.registries.localizationEntries = loaded.entries;
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
defineTags(event) {
|
|
706
|
+
const dictionary = event && event.dictionary;
|
|
707
|
+
|
|
708
|
+
if (!dictionary || typeof dictionary.defineTag !== "function") {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
dictionary.defineTag("hiaLocale", {
|
|
713
|
+
canHaveValue: true,
|
|
714
|
+
onTagged(doclet, tag) {
|
|
715
|
+
rememberTag(doclet, "locale", tag.value || tag.text || "");
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
dictionary.defineTag("hiaKey", {
|
|
720
|
+
canHaveValue: true,
|
|
721
|
+
onTagged(doclet, tag) {
|
|
722
|
+
rememberTag(doclet, "key", tag.value || tag.text || "");
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
dictionary.defineTag("hiaPath", {
|
|
727
|
+
canHaveValue: true,
|
|
728
|
+
onTagged(doclet, tag) {
|
|
729
|
+
rememberTag(doclet, "path", tag.value || tag.text || "");
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
dictionary.defineTag("lang", {
|
|
734
|
+
canHaveValue: true,
|
|
735
|
+
onTagged(doclet, tag) {
|
|
736
|
+
appendTag(doclet, "lang", tag.value || tag.text || "");
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
dictionary.defineTag("hiaText", {
|
|
741
|
+
canHaveValue: true,
|
|
742
|
+
onTagged(doclet, tag) {
|
|
743
|
+
appendTag(doclet, "text", tag.value || tag.text || "");
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
dictionary.defineTag("hiaBlock", {
|
|
748
|
+
canHaveValue: true,
|
|
749
|
+
onTagged(doclet, tag) {
|
|
750
|
+
appendTag(doclet, "block", tag.value || tag.text || "");
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
newDoclet(event, context) {
|
|
756
|
+
if (!event || !event.doclet) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const doclet = event.doclet;
|
|
761
|
+
const metadata = context.markMicroPlugin(doclet, this.name);
|
|
762
|
+
const tags = doclet.hiaTags || {};
|
|
763
|
+
const key = buildLocalizationKey(doclet, tags);
|
|
764
|
+
const descriptionBlocks = buildDescriptionBlocks(doclet, tags, context);
|
|
765
|
+
|
|
766
|
+
addResourceBlocks(descriptionBlocks, key, context);
|
|
767
|
+
|
|
768
|
+
const fields = buildFields(doclet, descriptionBlocks, context);
|
|
769
|
+
const descriptionField = fields.description || null;
|
|
770
|
+
const localized = buildCompatLocalized(descriptionField, context);
|
|
771
|
+
const missingLocales = hasLocalizationIntent(tags) && descriptionField
|
|
772
|
+
? descriptionField.missingLocales
|
|
773
|
+
: [];
|
|
774
|
+
|
|
775
|
+
diagnoseMissingDescriptionLocales(descriptionField, context, doclet, tags);
|
|
776
|
+
|
|
777
|
+
metadata.i18n = {
|
|
778
|
+
enabled: Boolean(context.config.i18n.enabled),
|
|
779
|
+
model: TEXT_I18N_MODEL,
|
|
780
|
+
modelVersion: TEXT_I18N_MODEL_VERSION,
|
|
781
|
+
defaultLocale: context.config.i18n.defaultLocale,
|
|
782
|
+
fallbackLocale: context.config.i18n.fallbackLocale,
|
|
783
|
+
locales: normalizeLocales(context.config),
|
|
784
|
+
mode: context.config.i18n.mode,
|
|
785
|
+
key: tags.key || "",
|
|
786
|
+
path: buildLocalizationPath(doclet, tags),
|
|
787
|
+
locale: tags.locale || "",
|
|
788
|
+
fields,
|
|
789
|
+
localized,
|
|
790
|
+
missingLocales,
|
|
791
|
+
resources: context.state.registries.localizationResources || [],
|
|
792
|
+
diagnostics: [],
|
|
793
|
+
generation: buildGenerationData(localized, fields, context)
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
};
|