react-cheminfo 0.23.0 → 0.25.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/README.md +39 -26
- package/lib/chrome/ui/HeaderToggle.d.ts +52 -0
- package/lib/chrome/ui/HeaderToggle.d.ts.map +1 -0
- package/lib/chrome/ui/HeaderToggle.js +31 -0
- package/lib/chrome/ui/HeaderToggle.js.map +1 -0
- package/lib/chrome/ui/index.d.ts +2 -0
- package/lib/chrome/ui/index.d.ts.map +1 -1
- package/lib/chrome/ui/index.js +1 -0
- package/lib/chrome/ui/index.js.map +1 -1
- package/lib/citation/core/gfn2Paper.d.ts +10 -0
- package/lib/citation/core/gfn2Paper.d.ts.map +1 -0
- package/lib/citation/core/gfn2Paper.js +28 -0
- package/lib/citation/core/gfn2Paper.js.map +1 -0
- package/lib/citation/core/index.d.ts +1 -0
- package/lib/citation/core/index.d.ts.map +1 -1
- package/lib/citation/core/index.js +1 -0
- package/lib/citation/core/index.js.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.d.ts +8 -7
- package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.js +3 -3
- package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
- package/lib/conformers.d.ts +27 -0
- package/lib/conformers.d.ts.map +1 -0
- package/lib/conformers.js +19 -0
- package/lib/conformers.js.map +1 -0
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/core/credits.d.ts +12 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +14 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/ecosystem/core/index.d.ts +1 -1
- package/lib/ecosystem/core/index.d.ts.map +1 -1
- package/lib/ecosystem/core/index.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts +11 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +11 -4
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.d.ts.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.js +3 -1
- package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
- package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
- package/lib/ecosystem/ui/SiteTile.js +3 -1
- package/lib/ecosystem/ui/SiteTile.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +6 -5
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/format/core/index.d.ts +1 -1
- package/lib/format/core/index.d.ts.map +1 -1
- package/lib/format/core/index.js +1 -1
- package/lib/format/core/index.js.map +1 -1
- package/lib/format/core/words.d.ts +9 -0
- package/lib/format/core/words.d.ts.map +1 -1
- package/lib/format/core/words.js +16 -0
- package/lib/format/core/words.js.map +1 -1
- package/lib/language/core/index.d.ts +3 -0
- package/lib/language/core/index.d.ts.map +1 -0
- package/lib/language/core/index.js +3 -0
- package/lib/language/core/index.js.map +1 -0
- package/lib/language/core/languageName.d.ts +8 -0
- package/lib/language/core/languageName.d.ts.map +1 -0
- package/lib/language/core/languageName.js +16 -0
- package/lib/language/core/languageName.js.map +1 -0
- package/lib/language/core/languageParam.d.ts +35 -0
- package/lib/language/core/languageParam.d.ts.map +1 -0
- package/lib/language/core/languageParam.js +67 -0
- package/lib/language/core/languageParam.js.map +1 -0
- package/lib/language/ui/SiteLanguage.d.ts +20 -0
- package/lib/language/ui/SiteLanguage.d.ts.map +1 -0
- package/lib/language/ui/SiteLanguage.js +14 -0
- package/lib/language/ui/SiteLanguage.js.map +1 -0
- package/lib/language/ui/index.d.ts +4 -0
- package/lib/language/ui/index.d.ts.map +1 -0
- package/lib/language/ui/index.js +3 -0
- package/lib/language/ui/index.js.map +1 -0
- package/lib/language/ui/siteLanguageContext.d.ts +12 -0
- package/lib/language/ui/siteLanguageContext.d.ts.map +1 -0
- package/lib/language/ui/siteLanguageContext.js +19 -0
- package/lib/language/ui/siteLanguageContext.js.map +1 -0
- package/lib/orbital/core/atomicOrbitals.d.ts +9 -1
- package/lib/orbital/core/atomicOrbitals.d.ts.map +1 -1
- package/lib/orbital/core/atomicOrbitals.js +7 -0
- package/lib/orbital/core/atomicOrbitals.js.map +1 -1
- package/lib/orbital/core/hydrogenic.d.ts +2 -1
- package/lib/orbital/core/hydrogenic.d.ts.map +1 -1
- package/lib/orbital/core/hydrogenic.js +2 -1
- package/lib/orbital/core/hydrogenic.js.map +1 -1
- package/lib/orbital/core/index.d.ts +1 -0
- package/lib/orbital/core/index.d.ts.map +1 -1
- package/lib/orbital/core/index.js +1 -0
- package/lib/orbital/core/index.js.map +1 -1
- package/lib/orbital/core/slaterEnergy.d.ts +35 -0
- package/lib/orbital/core/slaterEnergy.d.ts.map +1 -0
- package/lib/orbital/core/slaterEnergy.js +66 -0
- package/lib/orbital/core/slaterEnergy.js.map +1 -0
- package/lib/shared/ui/MenuButton.d.ts +3 -2
- package/lib/shared/ui/MenuButton.d.ts.map +1 -1
- package/lib/shared/ui/MenuButton.js +1 -1
- package/lib/shared/ui/MenuButton.js.map +1 -1
- package/lib/structure/core/conformerMinimise.d.ts +36 -0
- package/lib/structure/core/conformerMinimise.d.ts.map +1 -0
- package/lib/structure/core/conformerMinimise.js +81 -0
- package/lib/structure/core/conformerMinimise.js.map +1 -0
- package/lib/structure/core/conformerMinimum.d.ts +62 -0
- package/lib/structure/core/conformerMinimum.d.ts.map +1 -0
- package/lib/structure/core/conformerMinimum.js +90 -0
- package/lib/structure/core/conformerMinimum.js.map +1 -0
- package/lib/structure/core/conformerOptions.d.ts +62 -0
- package/lib/structure/core/conformerOptions.d.ts.map +1 -0
- package/lib/structure/core/conformerOptions.js +83 -0
- package/lib/structure/core/conformerOptions.js.map +1 -0
- package/lib/structure/core/conformerRefine.d.ts +104 -0
- package/lib/structure/core/conformerRefine.d.ts.map +1 -0
- package/lib/structure/core/conformerRefine.js +130 -0
- package/lib/structure/core/conformerRefine.js.map +1 -0
- package/lib/structure/core/conformerRefineRank.d.ts +36 -0
- package/lib/structure/core/conformerRefineRank.d.ts.map +1 -0
- package/lib/structure/core/conformerRefineRank.js +69 -0
- package/lib/structure/core/conformerRefineRank.js.map +1 -0
- package/lib/structure/core/conformerSession.d.ts +50 -0
- package/lib/structure/core/conformerSession.d.ts.map +1 -0
- package/lib/structure/core/conformerSession.js +88 -0
- package/lib/structure/core/conformerSession.js.map +1 -0
- package/lib/structure/core/conformerShape.d.ts +36 -0
- package/lib/structure/core/conformerShape.d.ts.map +1 -0
- package/lib/structure/core/conformerShape.js +150 -0
- package/lib/structure/core/conformerShape.js.map +1 -0
- package/lib/structure/core/conformers.d.ts +107 -0
- package/lib/structure/core/conformers.d.ts.map +1 -0
- package/lib/structure/core/conformers.js +149 -0
- package/lib/structure/core/conformers.js.map +1 -0
- package/lib/structure/core/geometryRelaxer.d.ts +66 -0
- package/lib/structure/core/geometryRelaxer.d.ts.map +1 -0
- package/lib/structure/core/geometryRelaxer.js +2 -0
- package/lib/structure/core/geometryRelaxer.js.map +1 -0
- package/lib/structure/core/index.d.ts +4 -0
- package/lib/structure/core/index.d.ts.map +1 -1
- package/lib/structure/core/index.js +2 -0
- package/lib/structure/core/index.js.map +1 -1
- package/lib/structure/core/moleculeCoordinates.d.ts +31 -0
- package/lib/structure/core/moleculeCoordinates.d.ts.map +1 -0
- package/lib/structure/core/moleculeCoordinates.js +80 -0
- package/lib/structure/core/moleculeCoordinates.js.map +1 -0
- package/lib/structure/core/molfileExport.d.ts +42 -0
- package/lib/structure/core/molfileExport.d.ts.map +1 -0
- package/lib/structure/core/molfileExport.js +47 -0
- package/lib/structure/core/molfileExport.js.map +1 -0
- package/lib/structure/core/oclResources.d.ts +12 -0
- package/lib/structure/core/oclResources.d.ts.map +1 -0
- package/lib/structure/core/oclResources.js +35 -0
- package/lib/structure/core/oclResources.js.map +1 -0
- package/lib/translate/core/catalog.d.ts +61 -0
- package/lib/translate/core/catalog.d.ts.map +1 -0
- package/lib/translate/core/catalog.js +49 -0
- package/lib/translate/core/catalog.js.map +1 -0
- package/lib/translate/core/checkTranslation.d.ts +32 -0
- package/lib/translate/core/checkTranslation.d.ts.map +1 -0
- package/lib/translate/core/checkTranslation.js +98 -0
- package/lib/translate/core/checkTranslation.js.map +1 -0
- package/lib/translate/core/contribution.d.ts +88 -0
- package/lib/translate/core/contribution.d.ts.map +1 -0
- package/lib/translate/core/contribution.js +14 -0
- package/lib/translate/core/contribution.js.map +1 -0
- package/lib/translate/core/index.d.ts +19 -0
- package/lib/translate/core/index.d.ts.map +1 -0
- package/lib/translate/core/index.js +15 -0
- package/lib/translate/core/index.js.map +1 -0
- package/lib/translate/core/marker.d.ts +45 -0
- package/lib/translate/core/marker.d.ts.map +1 -0
- package/lib/translate/core/marker.js +79 -0
- package/lib/translate/core/marker.js.map +1 -0
- package/lib/translate/core/mergeMessages.d.ts +35 -0
- package/lib/translate/core/mergeMessages.d.ts.map +1 -0
- package/lib/translate/core/mergeMessages.js +69 -0
- package/lib/translate/core/mergeMessages.js.map +1 -0
- package/lib/translate/core/session.d.ts +102 -0
- package/lib/translate/core/session.d.ts.map +1 -0
- package/lib/translate/core/session.js +162 -0
- package/lib/translate/core/session.js.map +1 -0
- package/lib/translate/core/start.d.ts +103 -0
- package/lib/translate/core/start.d.ts.map +1 -0
- package/lib/translate/core/start.js +92 -0
- package/lib/translate/core/start.js.map +1 -0
- package/lib/translate/core/suggestions.d.ts +77 -0
- package/lib/translate/core/suggestions.d.ts.map +1 -0
- package/lib/translate/core/suggestions.js +143 -0
- package/lib/translate/core/suggestions.js.map +1 -0
- package/lib/translate/core/tables.d.ts +100 -0
- package/lib/translate/core/tables.d.ts.map +1 -0
- package/lib/translate/core/tables.js +51 -0
- package/lib/translate/core/tables.js.map +1 -0
- package/lib/translate.d.ts +2 -0
- package/lib/translate.d.ts.map +1 -0
- package/lib/translate.js +6 -0
- package/lib/translate.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/lib/xtb/core/xtbRelaxer.d.ts +61 -0
- package/lib/xtb/core/xtbRelaxer.d.ts.map +1 -0
- package/lib/xtb/core/xtbRelaxer.js +108 -0
- package/lib/xtb/core/xtbRelaxer.js.map +1 -0
- package/lib/xtb.d.ts +11 -0
- package/lib/xtb.d.ts.map +1 -0
- package/lib/xtb.js +10 -0
- package/lib/xtb.js.map +1 -0
- package/package.json +18 -11
- package/src/chrome/ui/HeaderToggle.tsx +100 -0
- package/src/chrome/ui/index.ts +2 -0
- package/src/citation/core/gfn2Paper.ts +32 -0
- package/src/citation/core/index.ts +1 -0
- package/src/clipboard/ui/ClickToCopy.tsx +8 -7
- package/src/conformers.ts +87 -0
- package/src/core.ts +1 -0
- package/src/credits/core/credits.ts +16 -0
- package/src/ecosystem/core/index.ts +1 -0
- package/src/ecosystem/core/sites.ts +24 -4
- package/src/ecosystem/ui/EcosystemLinks.tsx +3 -1
- package/src/ecosystem/ui/SiteTile.tsx +3 -1
- package/src/ecosystem/ui/glyphs.tsx +16 -22
- package/src/format/core/index.ts +1 -1
- package/src/format/core/words.ts +19 -0
- package/src/language/core/index.ts +7 -0
- package/src/language/core/languageName.ts +16 -0
- package/src/language/core/languageParam.ts +70 -0
- package/src/language/ui/SiteLanguage.tsx +26 -0
- package/src/language/ui/index.ts +3 -0
- package/src/language/ui/siteLanguageContext.ts +20 -0
- package/src/orbital/core/atomicOrbitals.ts +17 -1
- package/src/orbital/core/hydrogenic.ts +2 -1
- package/src/orbital/core/index.ts +1 -0
- package/src/orbital/core/slaterEnergy.ts +83 -0
- package/src/shared/ui/MenuButton.tsx +4 -3
- package/src/structure/core/conformerMinimise.ts +99 -0
- package/src/structure/core/conformerMinimum.ts +125 -0
- package/src/structure/core/conformerOptions.ts +125 -0
- package/src/structure/core/conformerRefine.ts +234 -0
- package/src/structure/core/conformerRefineRank.ts +89 -0
- package/src/structure/core/conformerSession.ts +123 -0
- package/src/structure/core/conformerShape.ts +181 -0
- package/src/structure/core/conformers.ts +282 -0
- package/src/structure/core/geometryRelaxer.ts +71 -0
- package/src/structure/core/index.ts +13 -0
- package/src/structure/core/moleculeCoordinates.ts +93 -0
- package/src/structure/core/molfileExport.ts +62 -0
- package/src/structure/core/oclResources.ts +41 -0
- package/src/translate/core/catalog.ts +83 -0
- package/src/translate/core/checkTranslation.ts +128 -0
- package/src/translate/core/contribution.ts +99 -0
- package/src/translate/core/index.ts +72 -0
- package/src/translate/core/marker.ts +85 -0
- package/src/translate/core/mergeMessages.ts +74 -0
- package/src/translate/core/session.ts +250 -0
- package/src/translate/core/start.ts +183 -0
- package/src/translate/core/suggestions.ts +211 -0
- package/src/translate/core/tables.ts +120 -0
- package/src/translate.ts +5 -0
- package/src/ui.ts +1 -0
- package/src/xtb/core/xtbRelaxer.ts +160 -0
- package/src/xtb.ts +16 -0
- package/styles/chrome.css +61 -30
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Molecule } from 'openchemlib';
|
|
2
|
+
|
|
3
|
+
import type { RelaxableGeometry } from './geometryRelaxer.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read the elements and 3D coordinates of a molecule into the flat form an
|
|
7
|
+
* engine takes.
|
|
8
|
+
* @param molecule - A molecule carrying 3D coordinates. Not modified.
|
|
9
|
+
* @returns Its geometry in ångström, one entry per atom of `getAllAtoms()`.
|
|
10
|
+
*/
|
|
11
|
+
export function readRelaxableGeometry(molecule: Molecule): RelaxableGeometry {
|
|
12
|
+
const atoms = molecule.getAllAtoms();
|
|
13
|
+
const elements = new Array<string>(atoms);
|
|
14
|
+
const coordinates = new Float64Array(atoms * 3);
|
|
15
|
+
for (let atom = 0; atom < atoms; atom++) {
|
|
16
|
+
elements[atom] = molecule.getAtomLabel(atom);
|
|
17
|
+
coordinates[atom * 3] = molecule.getAtomX(atom);
|
|
18
|
+
coordinates[atom * 3 + 1] = molecule.getAtomY(atom);
|
|
19
|
+
coordinates[atom * 3 + 2] = molecule.getAtomZ(atom);
|
|
20
|
+
}
|
|
21
|
+
return { elements, coordinates };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Write flat coordinates back onto a molecule, atom for atom.
|
|
26
|
+
* @param molecule - The molecule to move. Mutated.
|
|
27
|
+
* @param coordinates - Flat `x,y,z` per atom in ångström.
|
|
28
|
+
* @throws {Error} When there is not one position per atom.
|
|
29
|
+
*/
|
|
30
|
+
export function writeRelaxedCoordinates(
|
|
31
|
+
molecule: Molecule,
|
|
32
|
+
coordinates: Float64Array,
|
|
33
|
+
): void {
|
|
34
|
+
const atoms = molecule.getAllAtoms();
|
|
35
|
+
if (coordinates.length !== atoms * 3) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`The relaxed geometry has ${coordinates.length / 3} positions for ${atoms} atoms.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
for (let atom = 0; atom < atoms; atom++) {
|
|
41
|
+
molecule.setAtomX(atom, coordinates[atom * 3] as number);
|
|
42
|
+
molecule.setAtomY(atom, coordinates[atom * 3 + 1] as number);
|
|
43
|
+
molecule.setAtomZ(atom, coordinates[atom * 3 + 2] as number);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Root-mean-square displacement between two geometries of the same atoms, each
|
|
49
|
+
* taken about its own centroid, in ångström.
|
|
50
|
+
*
|
|
51
|
+
* Removing the centroids is what makes the number mean "how far the atoms
|
|
52
|
+
* moved" rather than "how far the molecule was translated": a relaxer is free
|
|
53
|
+
* to recentre a molecule, and the `GeometryRelaxer` contract forbids it to
|
|
54
|
+
* rotate one.
|
|
55
|
+
* @param before - Flat coordinates before.
|
|
56
|
+
* @param after - Flat coordinates after.
|
|
57
|
+
* @returns The RMSD in ångström, `0` for an empty geometry.
|
|
58
|
+
* @throws {Error} When the two do not hold the same number of atoms.
|
|
59
|
+
*/
|
|
60
|
+
export function centredRmsd(before: Float64Array, after: Float64Array): number {
|
|
61
|
+
if (before.length !== after.length) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Cannot compare ${before.length / 3} positions with ${after.length / 3}.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const atoms = before.length / 3;
|
|
67
|
+
if (atoms === 0) return 0;
|
|
68
|
+
const centreBefore = centroid(before, atoms);
|
|
69
|
+
const centreAfter = centroid(after, atoms);
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (let atom = 0; atom < atoms; atom++) {
|
|
72
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
73
|
+
const gap =
|
|
74
|
+
(before[atom * 3 + axis] as number) -
|
|
75
|
+
(centreBefore[axis] as number) -
|
|
76
|
+
((after[atom * 3 + axis] as number) - (centreAfter[axis] as number));
|
|
77
|
+
total += gap * gap;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return Math.sqrt(total / atoms);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function centroid(coordinates: Float64Array, atoms: number): Float64Array {
|
|
84
|
+
let x = 0;
|
|
85
|
+
let y = 0;
|
|
86
|
+
let z = 0;
|
|
87
|
+
for (let atom = 0; atom < atoms; atom++) {
|
|
88
|
+
x += coordinates[atom * 3] as number;
|
|
89
|
+
y += coordinates[atom * 3 + 1] as number;
|
|
90
|
+
z += coordinates[atom * 3 + 2] as number;
|
|
91
|
+
}
|
|
92
|
+
return new Float64Array([x / atoms, y / atoms, z / atoms]);
|
|
93
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Molecule } from 'openchemlib';
|
|
2
|
+
|
|
3
|
+
import { classifyMolfile } from './molfile.ts';
|
|
4
|
+
|
|
5
|
+
/** A molfile plus the molstar format id that can actually parse it. */
|
|
6
|
+
export interface MolfileExport {
|
|
7
|
+
/** molstar's format id: `mol` reads V2000, `sdf` reads V2000 and V3000. */
|
|
8
|
+
format: 'mol' | 'sdf';
|
|
9
|
+
/** The molfile text, exactly as OpenChemLib wrote it. */
|
|
10
|
+
data: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Serialise a molecule for the 3D viewer and for export.
|
|
15
|
+
*
|
|
16
|
+
* V2000 is written whenever it can be: its counts line holds three digits per
|
|
17
|
+
* field, so above 999 atoms OpenChemLib writes `?` where the count belongs and
|
|
18
|
+
* the file becomes unreadable. V3000 is used from that size on.
|
|
19
|
+
*
|
|
20
|
+
* Coordinates are emitted exactly as OpenChemLib writes them — the whole
|
|
21
|
+
* molecule is negated in y and z relative to `getAtomY/Z`, which is a rigid
|
|
22
|
+
* rotation and therefore invisible to the viewer, to the energies and to any
|
|
23
|
+
* other file consumer.
|
|
24
|
+
*
|
|
25
|
+
* Hydrogens are in the file only if they are explicit atoms, so call this on a
|
|
26
|
+
* conformer, which always carries them.
|
|
27
|
+
* @param molecule - Structure with 3D coordinates already set. Not mutated.
|
|
28
|
+
* @returns The molfile and the format id to hand molstar.
|
|
29
|
+
* @throws {Error} When the molfile carries no atoms.
|
|
30
|
+
*/
|
|
31
|
+
export function toMolfileExport(molecule: Molecule): MolfileExport {
|
|
32
|
+
const data =
|
|
33
|
+
molecule.getAllAtoms() > V2000_MAX_ATOMS
|
|
34
|
+
? molecule.toMolfileV3()
|
|
35
|
+
: molecule.toMolfile();
|
|
36
|
+
return readMolfileExport(data);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pick the molstar format id for a molfile of unknown version, and refuse the
|
|
41
|
+
* ones that would render nothing.
|
|
42
|
+
*
|
|
43
|
+
* molstar's `mol` parser does not understand a V3000 CTAB: it returns zero atoms
|
|
44
|
+
* with `isError: false`, so the viewer shows an empty scene and reports no
|
|
45
|
+
* error. The version is read from the text rather than assumed from whichever
|
|
46
|
+
* writer produced it, because the text is the only thing molstar itself looks
|
|
47
|
+
* at.
|
|
48
|
+
* @param data - A molfile, V2000 or V3000.
|
|
49
|
+
* @returns The format id paired with the unchanged text.
|
|
50
|
+
* @throws {Error} When the molfile carries no atoms — the silent failure this guards.
|
|
51
|
+
*/
|
|
52
|
+
export function readMolfileExport(data: string): MolfileExport {
|
|
53
|
+
const { version, atomCount } = classifyMolfile(data);
|
|
54
|
+
if (atomCount === 0) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'Molfile has an empty atom block: molstar would render an empty scene without reporting an error.',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return { format: version === 'v3000' ? 'sdf' : 'mol', data };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const V2000_MAX_ATOMS = 999;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Resources } from 'openchemlib';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register OpenChemLib's static resources once, without which
|
|
5
|
+
* `ConformerGenerator` and `ForceFieldMMFF94` throw on construction.
|
|
6
|
+
*
|
|
7
|
+
* Repeated calls share the first call's promise; a failed registration is
|
|
8
|
+
* forgotten so a later call can retry.
|
|
9
|
+
* @param url - Where to fetch `resources.json`. Omit under Node.js to read it
|
|
10
|
+
* from the installed package; in a browser or a worker it is required.
|
|
11
|
+
* @returns A promise that settles once the resources are usable.
|
|
12
|
+
*/
|
|
13
|
+
export function registerResources(url?: string): Promise<void> {
|
|
14
|
+
registration ??= register(url).catch((error: unknown) => {
|
|
15
|
+
registration = null;
|
|
16
|
+
throw error;
|
|
17
|
+
});
|
|
18
|
+
return registration;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let registration: Promise<void> | null = null;
|
|
22
|
+
|
|
23
|
+
async function register(url?: string): Promise<void> {
|
|
24
|
+
if (url !== undefined) {
|
|
25
|
+
await Resources.registerFromUrl(url);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const runtime = globalThis as { process?: { versions?: { node?: string } } };
|
|
29
|
+
if (runtime.process?.versions?.node === undefined) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'registerResources needs a url outside Node.js: a browser page or worker cannot read resources.json from node_modules',
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const { createRequire } = await import('node:module');
|
|
35
|
+
const require = createRequire(import.meta.url);
|
|
36
|
+
Resources.registerFromNodejs(
|
|
37
|
+
require
|
|
38
|
+
.resolve('openchemlib')
|
|
39
|
+
.replace(/openchemlib\.js$/, 'resources.json'),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the messages a site or a library owns, and where they are kept.
|
|
3
|
+
*
|
|
4
|
+
* A catalog is one directory of a repository holding `en.json`, the source of
|
|
5
|
+
* truth, and one flat file per locale beside it. The site registers each of
|
|
6
|
+
* its catalogs with the repository and the directory, so a translation made
|
|
7
|
+
* anywhere on the page can be sent back to the file it belongs to.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Messages by key; every value is an ICU MessageFormat string. */
|
|
11
|
+
export type Messages = Record<string, string>;
|
|
12
|
+
|
|
13
|
+
/** The locale every catalog is written in first. */
|
|
14
|
+
export const SOURCE_LOCALE = 'en';
|
|
15
|
+
|
|
16
|
+
/** One catalog a page renders messages from. */
|
|
17
|
+
export interface CatalogSource {
|
|
18
|
+
/**
|
|
19
|
+
* The name the page knows the catalog by, unique on the page, e.g.
|
|
20
|
+
* `translate.cheminfo.org` or `react-cheminfo/share`.
|
|
21
|
+
*/
|
|
22
|
+
id: string;
|
|
23
|
+
/** The GitHub repository the files live in, written `owner/repo`. */
|
|
24
|
+
repository: string;
|
|
25
|
+
/**
|
|
26
|
+
* The directory holding `en.json` and the locale files, relative to the
|
|
27
|
+
* repository root, e.g. `frontend/src/locales`.
|
|
28
|
+
*/
|
|
29
|
+
directory: string;
|
|
30
|
+
/** The English messages. */
|
|
31
|
+
messages: Messages;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Which message of which catalog. */
|
|
35
|
+
export interface MessageRef {
|
|
36
|
+
/** The catalog, by the id the page registered it under. */
|
|
37
|
+
catalogId: string;
|
|
38
|
+
/** The key of the message in that catalog. */
|
|
39
|
+
key: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DIRECTORY_SEGMENT = /^[\w-][\w.-]*$/;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The message a catalog holds under a key, never one inherited from
|
|
46
|
+
* `Object.prototype`: a key like `constructor` is only a message when the
|
|
47
|
+
* catalog defines it.
|
|
48
|
+
* @param messages - The catalog's messages.
|
|
49
|
+
* @param key - The key asked for.
|
|
50
|
+
* @returns The message, or `undefined` when the catalog has none.
|
|
51
|
+
*/
|
|
52
|
+
export function ownMessage(
|
|
53
|
+
messages: Messages,
|
|
54
|
+
key: string,
|
|
55
|
+
): string | undefined {
|
|
56
|
+
return Object.hasOwn(messages, key) ? messages[key] : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether a path can name a catalog directory: relative, without `.` or `..`
|
|
61
|
+
* segments, and ending in a directory called `locales`. The server only ever
|
|
62
|
+
* writes inside such a directory.
|
|
63
|
+
* @param value - The candidate path, e.g. `frontend/src/locales`.
|
|
64
|
+
* @returns True when the path is acceptable.
|
|
65
|
+
*/
|
|
66
|
+
export function isCatalogDirectory(value: string): boolean {
|
|
67
|
+
const segments = value.split('/');
|
|
68
|
+
if (segments.at(-1) !== 'locales') return false;
|
|
69
|
+
for (const segment of segments) {
|
|
70
|
+
if (!DIRECTORY_SEGMENT.test(segment)) return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The file a catalog keeps one locale's messages in.
|
|
77
|
+
* @param directory - The catalog directory.
|
|
78
|
+
* @param locale - The locale.
|
|
79
|
+
* @returns The path, relative to the repository root.
|
|
80
|
+
*/
|
|
81
|
+
export function catalogFile(directory: string, locale: string): string {
|
|
82
|
+
return `${directory}/${locale}.json`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { MessageFormatElement } from '@formatjs/icu-messageformat-parser';
|
|
2
|
+
import { TYPE, parse } from '@formatjs/icu-messageformat-parser';
|
|
3
|
+
|
|
4
|
+
/** What can be wrong with one translated message. */
|
|
5
|
+
export type MessageProblemKind =
|
|
6
|
+
| 'empty'
|
|
7
|
+
| 'syntax'
|
|
8
|
+
| 'source-syntax'
|
|
9
|
+
| 'missing-argument'
|
|
10
|
+
| 'unknown-argument';
|
|
11
|
+
|
|
12
|
+
/** One thing wrong with a translated message, worded for the translator. */
|
|
13
|
+
export interface MessageProblem {
|
|
14
|
+
/** What kind of problem it is. */
|
|
15
|
+
kind: MessageProblemKind;
|
|
16
|
+
/** The sentence the translator reads. */
|
|
17
|
+
message: string;
|
|
18
|
+
/**
|
|
19
|
+
* The placeholder or tag the problem is about, written as it appears in a
|
|
20
|
+
* message: `{count}` or `<link>`.
|
|
21
|
+
* @default undefined
|
|
22
|
+
*/
|
|
23
|
+
argument?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check a translation against the English message it translates: it must be
|
|
28
|
+
* a valid ICU message and use exactly the placeholders and tags of the source.
|
|
29
|
+
* @param source - The English message.
|
|
30
|
+
* @param translation - The translated message.
|
|
31
|
+
* @returns Every problem found; empty when the translation can be used.
|
|
32
|
+
*/
|
|
33
|
+
export function checkTranslation(
|
|
34
|
+
source: string,
|
|
35
|
+
translation: string,
|
|
36
|
+
): MessageProblem[] {
|
|
37
|
+
if (translation.trim() === '') {
|
|
38
|
+
return [{ kind: 'empty', message: 'The translation is empty.' }];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let expected: Set<string>;
|
|
42
|
+
try {
|
|
43
|
+
expected = messageArguments(source);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
kind: 'source-syntax',
|
|
48
|
+
message: `The English message itself is not valid: ${describe(error)}`,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let actual: Set<string>;
|
|
54
|
+
try {
|
|
55
|
+
actual = messageArguments(translation);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return [{ kind: 'syntax', message: describe(error) }];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const problems: MessageProblem[] = [];
|
|
61
|
+
for (const argument of expected) {
|
|
62
|
+
if (!actual.has(argument)) {
|
|
63
|
+
problems.push({
|
|
64
|
+
kind: 'missing-argument',
|
|
65
|
+
argument,
|
|
66
|
+
message: `${argument} is in the English message but not in the translation.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const argument of actual) {
|
|
71
|
+
if (!expected.has(argument)) {
|
|
72
|
+
problems.push({
|
|
73
|
+
kind: 'unknown-argument',
|
|
74
|
+
argument,
|
|
75
|
+
message: `${argument} is not in the English message, so nothing would fill it.`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return problems;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The placeholders and tags a message uses, each written as it appears in the
|
|
84
|
+
* message: `{count}` for an argument, `<link>` for a tag.
|
|
85
|
+
* @param message - An ICU message.
|
|
86
|
+
* @returns The set of placeholders and tags.
|
|
87
|
+
* @throws {SyntaxError} When the message does not parse.
|
|
88
|
+
*/
|
|
89
|
+
export function messageArguments(message: string): Set<string> {
|
|
90
|
+
const found = new Set<string>();
|
|
91
|
+
collect(parse(message), found);
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function collect(elements: MessageFormatElement[], found: Set<string>): void {
|
|
96
|
+
for (const element of elements) {
|
|
97
|
+
switch (element.type) {
|
|
98
|
+
case TYPE.argument:
|
|
99
|
+
case TYPE.number:
|
|
100
|
+
case TYPE.date:
|
|
101
|
+
case TYPE.time:
|
|
102
|
+
found.add(`{${element.value}}`);
|
|
103
|
+
break;
|
|
104
|
+
case TYPE.select:
|
|
105
|
+
case TYPE.plural:
|
|
106
|
+
found.add(`{${element.value}}`);
|
|
107
|
+
for (const option of Object.values(element.options)) {
|
|
108
|
+
collect(option.value, found);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case TYPE.tag:
|
|
112
|
+
found.add(`<${element.value}>`);
|
|
113
|
+
collect(element.children, found);
|
|
114
|
+
break;
|
|
115
|
+
case TYPE.literal:
|
|
116
|
+
case TYPE.pound:
|
|
117
|
+
break;
|
|
118
|
+
// no default
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function describe(error: unknown): string {
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
return `The message does not parse (${error.message}).`;
|
|
126
|
+
}
|
|
127
|
+
return 'The message does not parse.';
|
|
128
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the overlay sends the server, and what the server answers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Messages } from './catalog.ts';
|
|
6
|
+
|
|
7
|
+
/** The longest message a contribution may carry, in characters. */
|
|
8
|
+
export const MAX_MESSAGE_LENGTH = 5000;
|
|
9
|
+
|
|
10
|
+
/** The most messages one catalog of a contribution may carry. */
|
|
11
|
+
export const MAX_MESSAGES_PER_CATALOG = 2000;
|
|
12
|
+
|
|
13
|
+
/** The most catalogs one contribution may carry. */
|
|
14
|
+
export const MAX_CATALOGS = 32;
|
|
15
|
+
|
|
16
|
+
/** The longest name a translator may be credited under. */
|
|
17
|
+
export const MAX_CONTRIBUTOR_LENGTH = 100;
|
|
18
|
+
|
|
19
|
+
/** The longest note a translator may leave for the reviewer. */
|
|
20
|
+
export const MAX_NOTE_LENGTH = 1000;
|
|
21
|
+
|
|
22
|
+
/** The messages translated for one catalog. */
|
|
23
|
+
export interface ContributionCatalog {
|
|
24
|
+
/** The repository the catalog lives in, written `owner/repo`. */
|
|
25
|
+
repository: string;
|
|
26
|
+
/** The catalog directory, relative to the repository root. */
|
|
27
|
+
directory: string;
|
|
28
|
+
/** The translated messages by key. */
|
|
29
|
+
messages: Messages;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One submission from the overlay. */
|
|
33
|
+
export interface Contribution {
|
|
34
|
+
/** The locale the messages are written in. */
|
|
35
|
+
locale: string;
|
|
36
|
+
/** The translated messages, catalog by catalog. */
|
|
37
|
+
catalogs: ContributionCatalog[];
|
|
38
|
+
/**
|
|
39
|
+
* The name the pull request credits.
|
|
40
|
+
* @default undefined
|
|
41
|
+
*/
|
|
42
|
+
contributor?: string;
|
|
43
|
+
/**
|
|
44
|
+
* A note for whoever reviews the pull request.
|
|
45
|
+
* @default undefined
|
|
46
|
+
*/
|
|
47
|
+
note?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Continuation tokens of pull requests this translator opened before: a
|
|
50
|
+
* repository that has an open one gets a new commit there, not a new PR.
|
|
51
|
+
* @default []
|
|
52
|
+
*/
|
|
53
|
+
continuations?: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A pull request a contribution opened or added to. */
|
|
57
|
+
export interface ContributionPullRequest {
|
|
58
|
+
/** The repository the pull request is on, `owner/repo`. */
|
|
59
|
+
repository: string;
|
|
60
|
+
/** Its number. */
|
|
61
|
+
number: number;
|
|
62
|
+
/** Where to read it. */
|
|
63
|
+
url: string;
|
|
64
|
+
/** How many messages this contribution changed in it. */
|
|
65
|
+
messageCount: number;
|
|
66
|
+
/** Whether an existing pull request was added to rather than opened. */
|
|
67
|
+
updated: boolean;
|
|
68
|
+
/** The token that lets the same translator add to it next time. */
|
|
69
|
+
continuation: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What a successful submission answers. */
|
|
73
|
+
export interface ContributionResult {
|
|
74
|
+
/** One entry per repository that changed; empty when nothing did. */
|
|
75
|
+
pullRequests: ContributionPullRequest[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One reason a submission was refused. */
|
|
79
|
+
export interface ContributionProblem {
|
|
80
|
+
/** The repository concerned. */
|
|
81
|
+
repository: string;
|
|
82
|
+
/** The catalog directory concerned. */
|
|
83
|
+
directory: string;
|
|
84
|
+
/**
|
|
85
|
+
* The message concerned, when the problem is about one.
|
|
86
|
+
* @default undefined
|
|
87
|
+
*/
|
|
88
|
+
key?: string;
|
|
89
|
+
/** The sentence the translator reads. */
|
|
90
|
+
message: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** What a refused submission answers. */
|
|
94
|
+
export interface ContributionRejection {
|
|
95
|
+
/** A summary of why. */
|
|
96
|
+
error: string;
|
|
97
|
+
/** Every problem found. */
|
|
98
|
+
problems: ContributionProblem[];
|
|
99
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Both travel with the catalogs: a server validating a submission and naming
|
|
2
|
+
// the language in a pull request must not have to load the whole of
|
|
3
|
+
// `react-cheminfo/core` to do it.
|
|
4
|
+
export { languageName } from '../../language/core/languageName.ts';
|
|
5
|
+
export { isLanguageTag } from '../../language/core/languageParam.ts';
|
|
6
|
+
export type { CatalogSource, MessageRef, Messages } from './catalog.ts';
|
|
7
|
+
export {
|
|
8
|
+
SOURCE_LOCALE,
|
|
9
|
+
catalogFile,
|
|
10
|
+
isCatalogDirectory,
|
|
11
|
+
ownMessage,
|
|
12
|
+
} from './catalog.ts';
|
|
13
|
+
export type { MessageProblem, MessageProblemKind } from './checkTranslation.ts';
|
|
14
|
+
export { checkTranslation, messageArguments } from './checkTranslation.ts';
|
|
15
|
+
export type {
|
|
16
|
+
Contribution,
|
|
17
|
+
ContributionCatalog,
|
|
18
|
+
ContributionProblem,
|
|
19
|
+
ContributionPullRequest,
|
|
20
|
+
ContributionRejection,
|
|
21
|
+
ContributionResult,
|
|
22
|
+
} from './contribution.ts';
|
|
23
|
+
export {
|
|
24
|
+
MAX_CATALOGS,
|
|
25
|
+
MAX_CONTRIBUTOR_LENGTH,
|
|
26
|
+
MAX_MESSAGES_PER_CATALOG,
|
|
27
|
+
MAX_MESSAGE_LENGTH,
|
|
28
|
+
MAX_NOTE_LENGTH,
|
|
29
|
+
} from './contribution.ts';
|
|
30
|
+
export {
|
|
31
|
+
encodeMarker,
|
|
32
|
+
hasMarker,
|
|
33
|
+
markText,
|
|
34
|
+
readMarkers,
|
|
35
|
+
stripMarkers,
|
|
36
|
+
} from './marker.ts';
|
|
37
|
+
export {
|
|
38
|
+
changedKeys,
|
|
39
|
+
mergeMessages,
|
|
40
|
+
parseMessages,
|
|
41
|
+
serializeMessages,
|
|
42
|
+
} from './mergeMessages.ts';
|
|
43
|
+
export type {
|
|
44
|
+
CatalogSnapshot,
|
|
45
|
+
MessageValues,
|
|
46
|
+
TranslateBridge,
|
|
47
|
+
TranslateSessionOptions,
|
|
48
|
+
} from './session.ts';
|
|
49
|
+
export { BRIDGE_GLOBAL, BRIDGE_PROTOCOL, TranslateSession } from './session.ts';
|
|
50
|
+
export type { LoadOverlayOptions, StartTranslatingOptions } from './start.ts';
|
|
51
|
+
export {
|
|
52
|
+
TRANSLATE_ORIGIN,
|
|
53
|
+
TRANSLATE_PARAM,
|
|
54
|
+
loadOverlay,
|
|
55
|
+
readTranslateLocale,
|
|
56
|
+
startTranslating,
|
|
57
|
+
} from './start.ts';
|
|
58
|
+
export type {
|
|
59
|
+
MessageSuggestion,
|
|
60
|
+
SuggestionKind,
|
|
61
|
+
SuggestionOptions,
|
|
62
|
+
TranslatedPair,
|
|
63
|
+
} from './suggestions.ts';
|
|
64
|
+
export { messageSimilarity, messageSuggestions } from './suggestions.ts';
|
|
65
|
+
export type {
|
|
66
|
+
TableCell,
|
|
67
|
+
TranslatableField,
|
|
68
|
+
TranslatableFieldSize,
|
|
69
|
+
TranslatableRow,
|
|
70
|
+
TranslatableTable,
|
|
71
|
+
} from './tables.ts';
|
|
72
|
+
export { tableCells, tableKey } from './tables.ts';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The invisible mark a message carries while a page is being translated.
|
|
3
|
+
*
|
|
4
|
+
* In translate mode every formatted message is prefixed with its id, written
|
|
5
|
+
* in characters that take no space and join nothing. Whatever the string then
|
|
6
|
+
* becomes — a text node, a `title`, a `placeholder`, a prop handed to a
|
|
7
|
+
* third-party component — the overlay finds it in the DOM and knows which
|
|
8
|
+
* message it is, without any component having been written for it.
|
|
9
|
+
*
|
|
10
|
+
* The digits are the invisible mathematical operators U+2061–U+2064, closed by
|
|
11
|
+
* WORD JOINER U+2060: none of them is whitespace, none joins emoji the way
|
|
12
|
+
* ZERO WIDTH JOINER does, and all are default-ignorable, so fonts draw nothing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const DIGITS = ['', '', '', ''] as const;
|
|
16
|
+
const END = '';
|
|
17
|
+
const BASE = DIGITS.length;
|
|
18
|
+
const FIRST_DIGIT = 0x20_61;
|
|
19
|
+
|
|
20
|
+
const MARKER = /[-]+/;
|
|
21
|
+
const MARKERS = /[-]+/g;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Write a message id as an invisible marker.
|
|
25
|
+
* @param id - The id, a non-negative integer.
|
|
26
|
+
* @returns The marker, to be put in front of the formatted message.
|
|
27
|
+
*/
|
|
28
|
+
export function encodeMarker(id: number): string {
|
|
29
|
+
if (!Number.isSafeInteger(id) || id < 0) {
|
|
30
|
+
throw new RangeError(`a marker id is a non-negative integer, got ${id}`);
|
|
31
|
+
}
|
|
32
|
+
let digits = '';
|
|
33
|
+
let rest = id;
|
|
34
|
+
do {
|
|
35
|
+
digits = `${DIGITS[rest % BASE]}${digits}`;
|
|
36
|
+
rest = Math.floor(rest / BASE);
|
|
37
|
+
} while (rest > 0);
|
|
38
|
+
return `${digits}${END}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prefix a formatted message with the marker of its id.
|
|
43
|
+
* @param text - The message as it will be shown.
|
|
44
|
+
* @param id - The id of the message.
|
|
45
|
+
* @returns The same text, with the invisible marker in front.
|
|
46
|
+
*/
|
|
47
|
+
export function markText(text: string, id: number): string {
|
|
48
|
+
return `${encodeMarker(id)}${text}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The ids of every message a string carries, in the order they appear.
|
|
53
|
+
* @param text - A text node's data or an attribute's value.
|
|
54
|
+
* @returns The ids; empty when the string holds no marker.
|
|
55
|
+
*/
|
|
56
|
+
export function readMarkers(text: string): number[] {
|
|
57
|
+
const ids: number[] = [];
|
|
58
|
+
for (const match of text.matchAll(MARKERS)) {
|
|
59
|
+
const marker = match[0];
|
|
60
|
+
let id = 0;
|
|
61
|
+
for (let index = 0; index < marker.length - 1; index++) {
|
|
62
|
+
id = id * BASE + (marker.codePointAt(index) ?? FIRST_DIGIT) - FIRST_DIGIT;
|
|
63
|
+
}
|
|
64
|
+
ids.push(id);
|
|
65
|
+
}
|
|
66
|
+
return ids;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether a string carries at least one marker.
|
|
71
|
+
* @param text - The string to look at.
|
|
72
|
+
* @returns True when a marker is present.
|
|
73
|
+
*/
|
|
74
|
+
export function hasMarker(text: string): boolean {
|
|
75
|
+
return MARKER.test(text);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The string with every marker removed, as it reads outside translate mode.
|
|
80
|
+
* @param text - A string that may carry markers.
|
|
81
|
+
* @returns The visible text only.
|
|
82
|
+
*/
|
|
83
|
+
export function stripMarkers(text: string): string {
|
|
84
|
+
return text.replaceAll(MARKERS, '');
|
|
85
|
+
}
|