@uniweb/build 0.80.1 → 0.80.2
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/package.json +6 -6
- package/src/i18n/merge.js +51 -1
- package/src/uwx/locale-sync.js +18 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.2",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
60
|
"@uniweb/content-reader": "^1.2.8",
|
|
61
|
-
"@uniweb/semantic-parser": "^1.5.1",
|
|
62
|
-
"@uniweb/projections": "^0.7.15",
|
|
63
|
-
"@uniweb/schemas": "^0.13.0",
|
|
64
61
|
"@uniweb/content-writer": "^0.3.4",
|
|
62
|
+
"@uniweb/schemas": "^0.13.0",
|
|
63
|
+
"@uniweb/projections": "^0.7.15",
|
|
64
|
+
"@uniweb/semantic-parser": "^1.5.1",
|
|
65
65
|
"@uniweb/theming": "^0.2.0"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"@tailwindcss/vite": "^4.0.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
76
76
|
"vite-plugin-svgr": "^4.0.0",
|
|
77
|
-
"@uniweb/
|
|
78
|
-
"@uniweb/
|
|
77
|
+
"@uniweb/runtime": "^0.29.4",
|
|
78
|
+
"@uniweb/core": "^0.38.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
81
81
|
"vite": {
|
package/src/i18n/merge.js
CHANGED
|
@@ -443,7 +443,7 @@ function linkRuns(source) {
|
|
|
443
443
|
runs.push({ link, marks: [...node.marks], before: gap })
|
|
444
444
|
}
|
|
445
445
|
gap = []
|
|
446
|
-
} else if (
|
|
446
|
+
} else if (isGap(node)) {
|
|
447
447
|
gap.push(node)
|
|
448
448
|
} else {
|
|
449
449
|
return null
|
|
@@ -452,6 +452,49 @@ function linkRuns(source) {
|
|
|
452
452
|
return runs.length ? runs : null
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// What may sit between two links of an element made only of links: a line break, or spaces.
|
|
456
|
+
function isGap(node) {
|
|
457
|
+
return node.type === 'hardBreak' || (node.type === 'text' && !node.marks?.length && !node.text?.trim())
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The labels of a translated element made only of links — `asLinkLabels` the other way, for a
|
|
462
|
+
* pull: one run of the target's own nodes per link of the source's, each without the marks its
|
|
463
|
+
* link carries. Null when the source is not only links, or the target's links are not the source's
|
|
464
|
+
* own, one to one and in order.
|
|
465
|
+
*
|
|
466
|
+
* ⚠️ It does not ask whether the merge reads the labels back to the target — a caller does
|
|
467
|
+
* (`uwx/locale-sync.js`), since only then are they the same translation.
|
|
468
|
+
*
|
|
469
|
+
* @param {Array} source - the element's source inline content
|
|
470
|
+
* @param {Array} target - the element's translated inline content
|
|
471
|
+
* @returns {Array<Array>|null} each link's label, as inline nodes
|
|
472
|
+
*/
|
|
473
|
+
export function linkLabels(source, target) {
|
|
474
|
+
const links = linkRuns(source)
|
|
475
|
+
if (!links) return null
|
|
476
|
+
const labels = []
|
|
477
|
+
let current = null
|
|
478
|
+
for (const node of target || []) {
|
|
479
|
+
if (!node) continue
|
|
480
|
+
const link = linkMarkOf(node)
|
|
481
|
+
if (link) {
|
|
482
|
+
if (!current || !sameMark(current, link)) {
|
|
483
|
+
const next = links[labels.length]
|
|
484
|
+
if (!next || !sameMark(next.link, link)) return null
|
|
485
|
+
labels.push([])
|
|
486
|
+
current = link
|
|
487
|
+
}
|
|
488
|
+
labels[labels.length - 1].push(withoutMarks(node, links[labels.length - 1].marks))
|
|
489
|
+
} else if (isGap(node)) {
|
|
490
|
+
current = null
|
|
491
|
+
} else {
|
|
492
|
+
return null
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return labels.length === links.length ? labels : null
|
|
496
|
+
}
|
|
497
|
+
|
|
455
498
|
function linkMarkOf(node) {
|
|
456
499
|
return node?.type === 'text' ? node.marks?.find((mark) => mark.type === 'link') || null : null
|
|
457
500
|
}
|
|
@@ -493,6 +536,13 @@ function withMarks(node, marks) {
|
|
|
493
536
|
return { ...node, marks: [...marks, ...own] }
|
|
494
537
|
}
|
|
495
538
|
|
|
539
|
+
// A label node without its link's marks — `withMarks` the other way.
|
|
540
|
+
function withoutMarks(node, marks) {
|
|
541
|
+
const own = (node.marks || []).filter((mark) => !marks.some((linkMark) => linkMark.type === mark.type))
|
|
542
|
+
const { marks: _linkMarks, ...rest } = node
|
|
543
|
+
return own.length ? { ...rest, marks: own } : rest
|
|
544
|
+
}
|
|
545
|
+
|
|
496
546
|
/**
|
|
497
547
|
* Translate the human-readable strings inside a section's TAGGED DATA BLOCKS,
|
|
498
548
|
* in place. The other half of the fix in `extract.js` — a manifest entry nobody
|
package/src/uwx/locale-sync.js
CHANGED
|
@@ -24,7 +24,7 @@ import { proseMirrorToMarkdown, serializeInlineContent } from '@uniweb/content-w
|
|
|
24
24
|
import { computeHash } from '../i18n/hash.js'
|
|
25
25
|
import { blockElements, elementText, dataBlockNodes } from '../i18n/extract.js'
|
|
26
26
|
import { visitDataBlockStrings } from '../i18n/data-strings.js'
|
|
27
|
-
import { resolveDocForLocale } from '../i18n/merge.js'
|
|
27
|
+
import { resolveDocForLocale, translatedInline, linkLabels } from '../i18n/merge.js'
|
|
28
28
|
import { computeSourceHash } from '../i18n/freeform-manifest.js'
|
|
29
29
|
import { sameMarkdownDocument, canonicalJson } from './same-content.js'
|
|
30
30
|
|
|
@@ -380,7 +380,7 @@ function deriveStructuralMap(sourceDoc, targetDoc, modelFor = () => null) {
|
|
|
380
380
|
const srcMd = serializeInlineContent(src[i].content || [])
|
|
381
381
|
const tgtMd = serializeInlineContent(tgt[i].content || [])
|
|
382
382
|
if (srcMd === tgtMd) continue // unchanged → untranslated → omit
|
|
383
|
-
map[key] = tgtMd
|
|
383
|
+
map[key] = labelsAlone(src[i].content || [], tgt[i].content || [], tgtMd) ?? tgtMd
|
|
384
384
|
}
|
|
385
385
|
} catch {
|
|
386
386
|
// a mark we can't serialize → don't risk a lossy map; store as free-form
|
|
@@ -389,6 +389,22 @@ function deriveStructuralMap(sourceDoc, targetDoc, modelFor = () => null) {
|
|
|
389
389
|
return addDataBlockStrings(map, sourceDoc, targetDoc, modelFor)
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
// ⭐ A LINK'S LABEL, NOT THE LINK, when only its words were translated. The merge reads a label onto
|
|
393
|
+
// the source's own link (`merge.js::translatedInline`), so it keeps following that link wherever the
|
|
394
|
+
// source moves it, where `[Donar Ahora](/contact)` would keep `/contact`. Several links are a label a
|
|
395
|
+
// line, as a translator writes them. Written only when the merge reads it back to this very element;
|
|
396
|
+
// a link a translator changed — the Spanish button pointed at a Spanish page — is written whole.
|
|
397
|
+
// ⛔ Until 2026-10-08 the element was always written whole: once the merge read a label (2026-10-01),
|
|
398
|
+
// a pull into the copy that pushed rewrote `"Donar Ahora"` as `"[Donar Ahora](/contact)"`, and split a
|
|
399
|
+
// string a page title and a button share into a default and per-place overrides.
|
|
400
|
+
function labelsAlone(source, target, whole) {
|
|
401
|
+
const labels = linkLabels(source, target)
|
|
402
|
+
if (!labels) return null
|
|
403
|
+
const value = labels.map((nodes) => serializeInlineContent(nodes)).join('\n')
|
|
404
|
+
const back = translatedInline(source, value)
|
|
405
|
+
return back && serializeInlineContent(back) === whole ? value : null
|
|
406
|
+
}
|
|
407
|
+
|
|
392
408
|
// ⭐ A TAGGED DATA BLOCK's translated strings, read pairwise — the strings the build translates
|
|
393
409
|
// (`visitDataBlockStrings`: a block's declared shape, else the extractor's heuristic), keyed by their
|
|
394
410
|
// source text as the build keys them. ⭐ Walked by the same model the push translated with — the pairing
|