twl-generator 1.0.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/LICENSE +21 -0
- package/README.md +155 -0
- package/package.json +60 -0
- package/src/cli.js +86 -0
- package/src/common/books.js +180 -0
- package/src/index.js +31 -0
- package/src/utils/twl-matcher.js +395 -0
- package/src/utils/usfm-alignment-remover.js +104 -0
- package/src/utils/zipProcessor.js +329 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 TWL Generator Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# twl-generator
|
|
2
|
+
|
|
3
|
+
Generate term-to-article lists from unfoldingWord en_tw archive for Bible books. Works in both Node.js (CLI) and React.js (browser) environments with intelligent caching.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Universal**: Works in Node.js and browser environments
|
|
8
|
+
- ✅ **Smart Caching**: File system (Node.js) or localStorage/sessionStorage (browser)
|
|
9
|
+
- ✅ **Performance**: Optimized matching with PrefixTrie algorithm
|
|
10
|
+
- ✅ **Case Sensitivity**: Proper God/god distinction (God→kt/god, god→kt/falsegod)
|
|
11
|
+
- ✅ **Morphological Variants**: Handles plurals, possessives, verb forms
|
|
12
|
+
- ✅ **Parentheses Normalization**: "Joseph (OT)" → "Joseph" for better coverage
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
### CLI (Node.js)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install globally
|
|
20
|
+
npm install -g twl-generator
|
|
21
|
+
|
|
22
|
+
# Generate TSV for a Bible book
|
|
23
|
+
twl-generator --book JHN --output john.tsv
|
|
24
|
+
|
|
25
|
+
# Process local USFM file
|
|
26
|
+
twl-generator --usfm my-file.usfm --output results.tsv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### React.js / Browser
|
|
30
|
+
|
|
31
|
+
```jsx
|
|
32
|
+
import { generateTWTerms, getCacheInfo, clearCache } from 'twl-generator/src/utils/zipProcessor.js';
|
|
33
|
+
|
|
34
|
+
function MyTWLComponent() {
|
|
35
|
+
const [terms, setTerms] = useState(null);
|
|
36
|
+
const [loading, setLoading] = useState(false);
|
|
37
|
+
|
|
38
|
+
const loadTerms = async (book) => {
|
|
39
|
+
setLoading(true);
|
|
40
|
+
try {
|
|
41
|
+
// First load: Downloads and processes (~3-4 seconds)
|
|
42
|
+
// Subsequent loads: Uses browser cache (~instant)
|
|
43
|
+
const termData = await generateTWTerms(book);
|
|
44
|
+
setTerms(termData);
|
|
45
|
+
|
|
46
|
+
// Debug cache info
|
|
47
|
+
console.log('Cache info:', getCacheInfo());
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Failed to load terms:', error);
|
|
50
|
+
}
|
|
51
|
+
setLoading(false);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleClearCache = async () => {
|
|
55
|
+
await clearCache();
|
|
56
|
+
console.log('Cache cleared - next load will download fresh data');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div>
|
|
61
|
+
{loading && <p>Loading translation words...</p>}
|
|
62
|
+
<button onClick={() => loadTerms('JHN')}>Load John</button>
|
|
63
|
+
<button onClick={() => loadTerms('RUT')}>Load Ruth</button>
|
|
64
|
+
<button onClick={handleClearCache}>Clear Cache</button>
|
|
65
|
+
{terms && <p>Loaded {Object.keys(terms).length} terms</p>}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Node.js Module
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
import { generateTWL } from 'twl-generator';
|
|
75
|
+
|
|
76
|
+
const result = await generateTWL('RUT');
|
|
77
|
+
console.log(result);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Browser Caching Strategy
|
|
81
|
+
|
|
82
|
+
The package uses a multi-tier caching approach for optimal performance in React.js:
|
|
83
|
+
|
|
84
|
+
1. **Memory Cache**: Fastest access during current session
|
|
85
|
+
2. **localStorage**: Persistent across browser sessions
|
|
86
|
+
3. **sessionStorage**: Fallback for private browsing mode
|
|
87
|
+
4. **Auto-regeneration**: Downloads fresh data when cache is invalid
|
|
88
|
+
|
|
89
|
+
### Cache Performance
|
|
90
|
+
|
|
91
|
+
- **Cold start** (no cache): ~3-4 seconds
|
|
92
|
+
- **Warm start** (browser cache): ~50-100ms
|
|
93
|
+
- **Hot start** (memory cache): ~1-5ms
|
|
94
|
+
|
|
95
|
+
## API Reference
|
|
96
|
+
|
|
97
|
+
### `generateTWTerms(book)`
|
|
98
|
+
|
|
99
|
+
Generate terms for a Bible book with caching.
|
|
100
|
+
|
|
101
|
+
- **book**: Bible book code (e.g., 'JHN', 'RUT', 'GEN')
|
|
102
|
+
- **Returns**: Promise<Object> - Term mapping object
|
|
103
|
+
|
|
104
|
+
### `clearCache()`
|
|
105
|
+
|
|
106
|
+
Clear all caches and force fresh download on next call.
|
|
107
|
+
|
|
108
|
+
- **Returns**: Promise<boolean> - Success status
|
|
109
|
+
|
|
110
|
+
### `getCacheInfo()`
|
|
111
|
+
|
|
112
|
+
Get cache status for debugging.
|
|
113
|
+
|
|
114
|
+
- **Returns**: Object with cache details
|
|
115
|
+
|
|
116
|
+
## Installation
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Global installation (for CLI usage)
|
|
120
|
+
npm install -g twl-generator
|
|
121
|
+
|
|
122
|
+
# Local installation (for React.js projects)
|
|
123
|
+
npm install twl-generator
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Supported Bible Books
|
|
127
|
+
|
|
128
|
+
All standard Bible book abbreviations are supported:
|
|
129
|
+
|
|
130
|
+
**Old Testament**: GEN, EXO, LEV, NUM, DEU, JOS, JDG, RUT, 1SA, 2SA, 1KI, 2KI, 1CH, 2CH, EZR, NEH, EST, JOB, PSA, PRO, ECC, SNG, ISA, JER, LAM, EZK, DAN, HOS, JOL, AMO, OBA, JON, MIC, NAH, HAB, ZEP, HAG, ZEC, MAL
|
|
131
|
+
|
|
132
|
+
**New Testament**: MAT, MRK, LUK, JHN, ACT, ROM, 1CO, 2CO, GAL, EPH, PHP, COL, 1TH, 2TH, 1TI, 2TI, TIT, PHM, HEB, JAS, 1PE, 2PE, 1JN, 2JN, 3JN, JUD, REV
|
|
133
|
+
|
|
134
|
+
## Output Format
|
|
135
|
+
|
|
136
|
+
The generated TSV contains these columns:
|
|
137
|
+
|
|
138
|
+
- **Reference**: Chapter:verse (e.g., "1:1")
|
|
139
|
+
- **ID**: Unique 4-character hex identifier
|
|
140
|
+
- **Tags**: Article category ("keyterm", "name", or empty)
|
|
141
|
+
- **OrigWords**: The matched text from the source
|
|
142
|
+
- **Occurrence**: Occurrence number for this term in this verse
|
|
143
|
+
- **TWLink**: Link to the translation words article
|
|
144
|
+
- **Disambiguation**: Multiple article options (if applicable)
|
|
145
|
+
- **Context**: Verse text with [matched term] in brackets
|
|
146
|
+
|
|
147
|
+
## Requirements
|
|
148
|
+
|
|
149
|
+
- **Node.js**: >=16.0.0
|
|
150
|
+
- **Browser**: Modern browser with ES6 modules support
|
|
151
|
+
- **React.js**: >=16.8.0 (for React usage)
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT License - see LICENSE file for details.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "twl-generator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generate term-to-article lists from unfoldingWord en_tw archive for Bible books. Works in both Node.js (CLI) and React.js (browser) environments.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"twl-generator": "src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"cli": "node src/cli.js",
|
|
13
|
+
"test": "node src/cli.js --book RUT --output test-output.tsv"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"bible",
|
|
17
|
+
"twl",
|
|
18
|
+
"generator",
|
|
19
|
+
"cli",
|
|
20
|
+
"react",
|
|
21
|
+
"browser",
|
|
22
|
+
"unfoldingword",
|
|
23
|
+
"translation-words"
|
|
24
|
+
],
|
|
25
|
+
"author": "Rich Mahn <richmahn@example.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"homepage": "https://github.com/yourusername/twl-generator#readme",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/yourusername/twl-generator.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/yourusername/twl-generator/issues"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src/cli.js",
|
|
37
|
+
"src/index.js",
|
|
38
|
+
"src/common/",
|
|
39
|
+
"src/utils/twl-matcher.js",
|
|
40
|
+
"src/utils/zipProcessor.js",
|
|
41
|
+
"src/utils/usfm-alignment-remover.js",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=16.0.0"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"adm-zip": "^0.5.10",
|
|
50
|
+
"node-fetch": "^3.3.2"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"react": ">=16.8.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"react": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateTWLWithUsfm } from './index.js';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
function printHelp() {
|
|
9
|
+
console.log(`Usage: generate-twls [options]
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--book <book> Specify the Bible book (e.g., rut)
|
|
13
|
+
--usfm <path> Path to USFM file to process
|
|
14
|
+
--output <path> Path to output TSV file
|
|
15
|
+
--help Show this help message
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
generate-twls --book rut
|
|
19
|
+
generate-twls --usfm ./41-MAT.usfm --output ./mat_twl.tsv
|
|
20
|
+
generate-twls --usfm ./file.usfm --book rut`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let book = null;
|
|
24
|
+
let usfmPath = null;
|
|
25
|
+
let outputPath = null;
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
if (args[i] === '--book' && args[i + 1]) {
|
|
29
|
+
book = args[i + 1].toLowerCase();
|
|
30
|
+
i++;
|
|
31
|
+
} else if (args[i] === '--usfm' && args[i + 1]) {
|
|
32
|
+
usfmPath = args[i + 1];
|
|
33
|
+
i++;
|
|
34
|
+
} else if (args[i] === '--output' && args[i + 1]) {
|
|
35
|
+
outputPath = args[i + 1];
|
|
36
|
+
i++;
|
|
37
|
+
} else if (args[i] === '--help') {
|
|
38
|
+
printHelp();
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate arguments
|
|
44
|
+
if (!book && !usfmPath) {
|
|
45
|
+
console.error('Error: Either --book or --usfm parameter is required');
|
|
46
|
+
printHelp();
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (usfmPath && !fs.existsSync(usfmPath)) {
|
|
51
|
+
console.error(`Error: USFM file not found: ${usfmPath}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
(async () => {
|
|
56
|
+
try {
|
|
57
|
+
let usfmContent = null;
|
|
58
|
+
if (usfmPath) {
|
|
59
|
+
usfmContent = fs.readFileSync(usfmPath, 'utf8');
|
|
60
|
+
console.log(`Reading USFM from: ${usfmPath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tsv = await generateTWLWithUsfm(book, usfmContent);
|
|
64
|
+
|
|
65
|
+
// Determine output filename
|
|
66
|
+
let filename;
|
|
67
|
+
if (outputPath) {
|
|
68
|
+
filename = outputPath;
|
|
69
|
+
} else if (book) {
|
|
70
|
+
filename = `twl_${book.toUpperCase()}.tsv`;
|
|
71
|
+
} else if (usfmPath) {
|
|
72
|
+
const baseName = path.basename(usfmPath, path.extname(usfmPath));
|
|
73
|
+
filename = `${baseName}.tsv`;
|
|
74
|
+
} else {
|
|
75
|
+
filename = 'output.tsv';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Save TSV to file
|
|
79
|
+
fs.writeFileSync(filename, tsv, 'utf8');
|
|
80
|
+
console.log(`TSV file saved as ${filename}`);
|
|
81
|
+
console.log(`Found ${tsv.split('\n').length - 1} matches`);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Error:', error.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export const BibleBookData = {
|
|
2
|
+
"gen": { "id": "gen", "title": "Genesis", "usfm": "01-GEN", "testament": "old", "verseCount": 1533, "chapters": [31, 25, 24, 26, 32, 22, 24, 22, 29, 32, 32, 20, 18, 24, 21, 16, 27, 33, 38, 18, 34, 24, 20, 67, 34, 35, 46, 22, 35, 43, 55, 32, 20, 31, 29, 43, 36, 30, 23, 23, 57, 38, 34, 34, 28, 34, 31, 22, 33, 26] },
|
|
3
|
+
"exo": { "id": "exo", "title": "Exodus", "usfm": "02-EXO", "testament": "old", "verseCount": 1213, "chapters": [22, 25, 22, 31, 23, 30, 25, 32, 35, 29, 10, 51, 22, 31, 27, 36, 16, 27, 25, 26, 36, 31, 33, 18, 40, 37, 21, 43, 46, 38, 18, 35, 23, 35, 35, 38, 29, 31, 43, 38] },
|
|
4
|
+
"lev": { "id": "lev", "title": "Leviticus", "usfm": "03-LEV", "testament": "old", "verseCount": 859, "chapters": [17, 16, 17, 35, 19, 30, 38, 36, 24, 20, 47, 8, 59, 57, 33, 34, 16, 30, 37, 27, 24, 33, 44, 23, 55, 46, 34] },
|
|
5
|
+
"num": { "id": "num", "title": "Numbers", "usfm": "04-NUM", "testament": "old", "verseCount": 1288, "chapters": [54, 34, 51, 49, 31, 27, 89, 26, 23, 36, 35, 16, 33, 45, 41, 50, 13, 32, 22, 29, 35, 41, 30, 25, 18, 65, 23, 31, 40, 16, 54, 42, 56, 29, 34, 13] },
|
|
6
|
+
"deu": { "id": "deu", "title": "Deuteronomy", "usfm": "05-DEU", "testament": "old", "verseCount": 959, "chapters": [46, 37, 29, 49, 33, 25, 26, 20, 29, 22, 32, 32, 18, 29, 23, 22, 20, 22, 21, 20, 23, 30, 25, 22, 19, 19, 26, 68, 29, 20, 30, 52, 29, 12] },
|
|
7
|
+
"jos": { "id": "jos", "title": "Joshua", "usfm": "06-JOS", "testament": "old", "verseCount": 658, "chapters": [18, 24, 17, 24, 15, 27, 26, 35, 27, 43, 23, 24, 33, 15, 63, 10, 18, 28, 51, 9, 45, 34, 16, 33] },
|
|
8
|
+
"jdg": { "id": "jdg", "title": "Judges", "usfm": "07-JDG", "testament": "old", "verseCount": 618, "chapters": [36, 23, 31, 24, 31, 40, 25, 35, 57, 18, 40, 15, 25, 20, 20, 31, 13, 31, 30, 48, 25] },
|
|
9
|
+
"rut": { "id": "rut", "title": "Ruth", "usfm": "08-RUT", "testament": "old", "verseCount": 85, "chapters": [22, 23, 18, 22] },
|
|
10
|
+
"1sa": { "id": "1sa", "title": "1 Samuel", "usfm": "09-1SA", "testament": "old", "verseCount": 810, "chapters": [28, 36, 21, 22, 12, 21, 17, 22, 27, 27, 15, 25, 23, 52, 35, 23, 58, 30, 24, 42, 15, 23, 29, 22, 44, 25, 12, 25, 11, 31, 13] },
|
|
11
|
+
"2sa": { "id": "2sa", "title": "2 Samuel", "usfm": "10-2SA", "testament": "old", "verseCount": 695, "chapters": [27, 32, 39, 12, 25, 23, 29, 18, 13, 19, 27, 31, 39, 33, 37, 23, 29, 33, 43, 26, 22, 51, 39, 25] },
|
|
12
|
+
"1ki": { "id": "1ki", "title": "1 Kings", "usfm": "11-1KI", "testament": "old", "verseCount": 816, "chapters": [53, 46, 28, 34, 18, 38, 51, 66, 28, 29, 43, 33, 34, 31, 34, 34, 24, 46, 21, 43, 29, 53] },
|
|
13
|
+
"2ki": { "id": "2ki", "title": "2 Kings", "usfm": "12-2KI", "testament": "old", "verseCount": 719, "chapters": [18, 25, 27, 44, 27, 33, 20, 29, 37, 36, 21, 21, 25, 29, 38, 20, 41, 37, 37, 21, 26, 20, 37, 20, 30] },
|
|
14
|
+
"1ch": { "id": "1ch", "title": "1 Chronicles", "usfm": "13-1CH", "testament": "old", "verseCount": 942, "chapters": [54, 55, 24, 43, 26, 81, 40, 40, 44, 14, 47, 40, 14, 17, 29, 43, 27, 17, 19, 8, 30, 19, 32, 31, 31, 32, 34, 21, 30] },
|
|
15
|
+
"2ch": { "id": "2ch", "title": "2 Chronicles", "usfm": "14-2CH", "testament": "old", "verseCount": 822, "chapters": [17, 18, 17, 22, 14, 42, 22, 18, 31, 19, 23, 16, 22, 15, 19, 14, 19, 34, 11, 37, 20, 12, 21, 27, 28, 23, 9, 27, 36, 27, 21, 33, 25, 33, 27, 23] },
|
|
16
|
+
"ezr": { "id": "ezr", "title": "Ezra", "usfm": "15-EZR", "testament": "old", "verseCount": 280, "chapters": [11, 70, 13, 24, 17, 22, 28, 36, 15, 44] },
|
|
17
|
+
"neh": { "id": "neh", "title": "Nehemiah", "usfm": "16-NEH", "testament": "old", "verseCount": 406, "chapters": [11, 20, 32, 23, 19, 19, 73, 18, 38, 39, 36, 47, 31] },
|
|
18
|
+
"est": { "id": "est", "title": "Esther", "usfm": "17-EST", "testament": "old", "verseCount": 167, "chapters": [22, 23, 15, 17, 14, 14, 10, 17, 32, 3] },
|
|
19
|
+
"job": { "id": "job", "title": "Job", "usfm": "18-JOB", "testament": "old", "verseCount": 1070, "chapters": [22, 13, 26, 21, 27, 30, 21, 22, 35, 22, 20, 25, 28, 22, 35, 22, 16, 21, 29, 29, 34, 30, 17, 25, 6, 14, 23, 28, 25, 31, 40, 22, 33, 37, 16, 33, 24, 41, 30, 24, 34, 17] },
|
|
20
|
+
"psa": { "id": "psa", "title": "Psalms", "usfm": "19-PSA", "testament": "old", "verseCount": 2461, "chapters": [6, 12, 8, 8, 12, 10, 17, 9, 20, 18, 7, 8, 6, 7, 5, 11, 15, 50, 14, 9, 13, 31, 6, 10, 22, 12, 14, 9, 11, 12, 24, 11, 22, 22, 28, 12, 40, 22, 13, 17, 13, 11, 5, 26, 17, 11, 9, 14, 20, 23, 19, 9, 6, 7, 23, 13, 11, 11, 17, 12, 8, 12, 11, 10, 13, 20, 7, 35, 36, 5, 24, 20, 28, 23, 10, 12, 20, 72, 13, 19, 16, 8, 18, 12, 13, 17, 7, 18, 52, 17, 16, 15, 5, 23, 11, 13, 12, 9, 9, 5, 8, 28, 22, 35, 45, 48, 43, 13, 31, 7, 10, 10, 9, 8, 18, 19, 2, 29, 176, 7, 8, 9, 4, 8, 5, 6, 5, 6, 8, 8, 3, 18, 3, 3, 21, 26, 9, 8, 24, 13, 10, 7, 12, 15, 21, 10, 20, 14, 9, 6] },
|
|
21
|
+
"pro": { "id": "pro", "title": "Proverbs", "usfm": "20-PRO", "testament": "old", "verseCount": 915, "chapters": [33, 22, 35, 27, 23, 35, 27, 36, 18, 32, 31, 28, 25, 35, 33, 33, 28, 24, 29, 30, 31, 29, 35, 34, 28, 28, 27, 28, 27, 33, 31] },
|
|
22
|
+
"ecc": { "id": "ecc", "title": "Ecclesiastes", "usfm": "21-ECC", "testament": "old", "verseCount": 222, "chapters": [18, 26, 22, 16, 20, 12, 29, 17, 18, 20, 10, 14] },
|
|
23
|
+
"sng": { "id": "sng", "title": "Song of Songs", "usfm": "22-SNG", "testament": "old", "verseCount": 117, "chapters": [17, 17, 11, 16, 16, 13, 13, 14] },
|
|
24
|
+
"isa": { "id": "isa", "title": "Isaiah", "usfm": "23-ISA", "testament": "old", "verseCount": 1292, "chapters": [31, 22, 26, 6, 30, 13, 25, 22, 21, 34, 16, 6, 22, 32, 9, 14, 14, 7, 25, 6, 17, 25, 18, 23, 12, 21, 13, 29, 24, 33, 9, 20, 24, 17, 10, 22, 38, 22, 8, 31, 29, 25, 28, 28, 25, 13, 15, 22, 26, 11, 23, 15, 12, 17, 13, 12, 21, 14, 21, 22, 11, 12, 19, 12, 25, 24] },
|
|
25
|
+
"jer": { "id": "jer", "title": "Jeremiah", "usfm": "24-JER", "testament": "old", "verseCount": 1364, "chapters": [19, 37, 25, 31, 31, 30, 34, 22, 26, 25, 23, 17, 27, 22, 21, 21, 27, 23, 15, 18, 14, 30, 40, 10, 38, 24, 22, 17, 32, 24, 40, 44, 26, 22, 19, 32, 21, 28, 18, 16, 18, 22, 13, 30, 5, 28, 7, 47, 39, 46, 64, 34] },
|
|
26
|
+
"lam": { "id": "lam", "title": "Lamentations", "usfm": "25-LAM", "testament": "old", "verseCount": 154, "chapters": [22, 22, 66, 22, 22] },
|
|
27
|
+
"ezk": { "id": "ezk", "title": "Ezekiel", "usfm": "26-EZK", "testament": "old", "verseCount": 1273, "chapters": [28, 10, 27, 17, 17, 14, 27, 18, 11, 22, 25, 28, 23, 23, 8, 63, 24, 32, 14, 49, 32, 31, 49, 27, 17, 21, 36, 26, 21, 26, 18, 32, 33, 31, 15, 38, 28, 23, 29, 49, 26, 20, 27, 31, 25, 24, 23, 35] },
|
|
28
|
+
"dan": { "id": "dan", "title": "Daniel", "usfm": "27-DAN", "testament": "old", "verseCount": 357, "chapters": [21, 49, 30, 37, 31, 28, 28, 27, 27, 21, 45, 13] },
|
|
29
|
+
"hos": { "id": "hos", "title": "Hosea", "usfm": "28-HOS", "testament": "old", "verseCount": 197, "chapters": [11, 23, 5, 19, 15, 11, 16, 14, 17, 15, 12, 14, 16, 9] },
|
|
30
|
+
"jol": { "id": "jol", "title": "Joel", "usfm": "29-JOL", "testament": "old", "verseCount": 73, "chapters": [20, 32, 21] },
|
|
31
|
+
"amo": { "id": "amo", "title": "Amos", "usfm": "30-AMO", "testament": "old", "verseCount": 146, "chapters": [15, 16, 15, 13, 27, 14, 17, 14, 15] },
|
|
32
|
+
"oba": { "id": "oba", "title": "Obadiah", "usfm": "31-OBA", "testament": "old", "verseCount": 21, "chapters": [21] },
|
|
33
|
+
"jon": { "id": "jon", "title": "Jonah", "usfm": "32-JON", "testament": "old", "verseCount": 48, "chapters": [17, 10, 10, 11] },
|
|
34
|
+
"mic": { "id": "mic", "title": "Micah", "usfm": "33-MIC", "testament": "old", "verseCount": 105, "chapters": [16, 13, 12, 13, 15, 16, 20] },
|
|
35
|
+
"nam": { "id": "nam", "title": "Nahum", "usfm": "34-NAM", "testament": "old", "verseCount": 47, "chapters": [15, 13, 19] },
|
|
36
|
+
"hab": { "id": "hab", "title": "Habakkuk", "usfm": "35-HAB", "testament": "old", "verseCount": 56, "chapters": [17, 20, 19] },
|
|
37
|
+
"zep": { "id": "zep", "title": "Zephaniah", "usfm": "36-ZEP", "testament": "old", "verseCount": 53, "chapters": [18, 15, 20] },
|
|
38
|
+
"hag": { "id": "hag", "title": "Haggai", "usfm": "37-HAG", "testament": "old", "verseCount": 38, "chapters": [15, 23] },
|
|
39
|
+
"zec": { "id": "zec", "title": "Zechariah", "usfm": "38-ZEC", "testament": "old", "verseCount": 211, "chapters": [21, 13, 10, 14, 11, 15, 14, 23, 17, 12, 17, 14, 9, 21] },
|
|
40
|
+
"mal": { "id": "mal", "title": "Malachi", "usfm": "39-MAL", "testament": "old", "verseCount": 55, "chapters": [14, 17, 18, 6] },
|
|
41
|
+
"mat": { "id": "mat", "title": "Matthew", "usfm": "41-MAT", "testament": "new", "verseCount": 1071, "chapters": [25, 23, 17, 25, 48, 34, 29, 34, 38, 42, 30, 50, 58, 36, 39, 28, 27, 35, 30, 34, 46, 46, 39, 51, 46, 75, 66, 20] },
|
|
42
|
+
"mrk": { "id": "mrk", "title": "Mark", "usfm": "42-MRK", "testament": "new", "verseCount": 678, "chapters": [45, 28, 35, 41, 43, 56, 37, 38, 50, 52, 33, 44, 37, 72, 47, 20] },
|
|
43
|
+
"luk": { "id": "luk", "title": "Luke", "usfm": "43-LUK", "testament": "new", "verseCount": 1151, "chapters": [80, 52, 38, 44, 39, 49, 50, 56, 62, 42, 54, 59, 35, 35, 32, 31, 37, 43, 48, 47, 38, 71, 56, 53] },
|
|
44
|
+
"jhn": { "id": "jhn", "title": "John", "usfm": "44-JHN", "testament": "new", "verseCount": 879, "chapters": [51, 25, 36, 54, 47, 71, 53, 59, 41, 42, 57, 50, 38, 31, 27, 33, 26, 40, 42, 31, 25] },
|
|
45
|
+
"act": { "id": "act", "title": "Acts", "usfm": "45-ACT", "testament": "new", "verseCount": 1007, "chapters": [26, 47, 26, 37, 42, 15, 60, 40, 43, 48, 30, 25, 52, 28, 41, 40, 34, 28, 41, 38, 40, 30, 35, 27, 27, 32, 44, 31] },
|
|
46
|
+
"rom": { "id": "rom", "title": "Romans", "usfm": "46-ROM", "testament": "new", "verseCount": 433, "chapters": [32, 29, 31, 25, 21, 23, 25, 39, 33, 21, 36, 21, 14, 23, 33, 27] },
|
|
47
|
+
"1co": { "id": "1co", "title": "1 Corinthians", "usfm": "47-1CO", "testament": "new", "verseCount": 437, "chapters": [31, 16, 23, 21, 13, 20, 40, 13, 27, 33, 34, 31, 13, 40, 58, 24] },
|
|
48
|
+
"2co": { "id": "2co", "title": "2 Corinthians", "usfm": "48-2CO", "testament": "new", "verseCount": 257, "chapters": [24, 17, 18, 18, 21, 18, 16, 24, 15, 18, 33, 21, 14] },
|
|
49
|
+
"gal": { "id": "gal", "title": "Galatians", "usfm": "49-GAL", "testament": "new", "verseCount": 149, "chapters": [24, 21, 29, 31, 26, 18] },
|
|
50
|
+
"eph": { "id": "eph", "title": "Ephesians", "usfm": "50-EPH", "testament": "new", "verseCount": 155, "chapters": [23, 22, 21, 32, 33, 24] },
|
|
51
|
+
"php": { "id": "php", "title": "Philippians", "usfm": "51-PHP", "testament": "new", "verseCount": 104, "chapters": [30, 30, 21, 23] },
|
|
52
|
+
"col": { "id": "col", "title": "Colossians", "usfm": "52-COL", "testament": "new", "verseCount": 95, "chapters": [29, 23, 25, 18] },
|
|
53
|
+
"1th": { "id": "1th", "title": "1 Thessalonians", "usfm": "53-1TH", "testament": "new", "verseCount": 89, "chapters": [10, 20, 13, 18, 28] },
|
|
54
|
+
"2th": { "id": "2th", "title": "2 Thessalonians", "usfm": "54-2TH", "testament": "new", "verseCount": 47, "chapters": [12, 17, 18] },
|
|
55
|
+
"1ti": { "id": "1ti", "title": "1 Timothy", "usfm": "55-1TI", "testament": "new", "verseCount": 113, "chapters": [20, 15, 16, 16, 25, 21] },
|
|
56
|
+
"2ti": { "id": "2ti", "title": "2 Timothy", "usfm": "56-2TI", "testament": "new", "verseCount": 83, "chapters": [18, 26, 17, 22] },
|
|
57
|
+
"tit": { "id": "tit", "title": "Titus", "usfm": "57-TIT", "testament": "new", "verseCount": 46, "chapters": [16, 15, 15] },
|
|
58
|
+
"phm": { "id": "phm", "title": "Philemon", "usfm": "58-PHM", "testament": "new", "verseCount": 25, "chapters": [25] },
|
|
59
|
+
"heb": { "id": "heb", "title": "Hebrews", "usfm": "59-HEB", "testament": "new", "verseCount": 303, "chapters": [14, 18, 19, 16, 14, 20, 28, 13, 28, 39, 40, 29, 25] },
|
|
60
|
+
"jas": { "id": "jas", "title": "James", "usfm": "60-JAS", "testament": "new", "verseCount": 108, "chapters": [27, 26, 18, 17, 20] },
|
|
61
|
+
"1pe": { "id": "1pe", "title": "1 Peter", "usfm": "61-1PE", "testament": "new", "verseCount": 105, "chapters": [25, 25, 22, 19, 14] },
|
|
62
|
+
"2pe": { "id": "2pe", "title": "2 Peter", "usfm": "62-2PE", "testament": "new", "verseCount": 61, "chapters": [21, 22, 18] },
|
|
63
|
+
"1jn": { "id": "1jn", "title": "1 John", "usfm": "63-1JN", "testament": "new", "verseCount": 105, "chapters": [10, 29, 24, 21, 21] },
|
|
64
|
+
"2jn": { "id": "2jn", "title": "2 John", "usfm": "64-2JN", "testament": "new", "verseCount": 13, "chapters": [13] },
|
|
65
|
+
"3jn": { "id": "3jn", "title": "3 John", "usfm": "65-3JN", "testament": "new", "verseCount": 15, "chapters": [15] },
|
|
66
|
+
"jud": { "id": "jud", "title": "Jude", "usfm": "66-JUD", "testament": "new", "verseCount": 25, "chapters": [25] },
|
|
67
|
+
"rev": { "id": "rev", "title": "Revelation", "usfm": "67-REV", "testament": "new", "verseCount": 404, "chapters": [20, 29, 22, 11, 14, 17, 17, 13, 21, 11, 19, 17, 18, 20, 8, 21, 18, 24, 21, 15, 27, 21] }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const oftenMissingBCVList = [
|
|
71
|
+
// See https://en.wikipedia.org/wiki/List_of_New_Testament_verses_not_included_in_modern_English_translations
|
|
72
|
+
['NEH', 7, 68], // ?
|
|
73
|
+
['MAT', 16, 3],
|
|
74
|
+
['MAT', 17, 21],
|
|
75
|
+
['MAT', 18, 11],
|
|
76
|
+
// ['MAT', 20, 16b],
|
|
77
|
+
['MAT', 23, 14],
|
|
78
|
+
// ['MRK', 6, 11b],
|
|
79
|
+
['MRK', 7, 16],
|
|
80
|
+
['MRK', 9, 44], ['MRK', 9, 46],
|
|
81
|
+
['MRK', 11, 26],
|
|
82
|
+
['MRK', 15, 28],
|
|
83
|
+
['MRK', 16, 9], ['MRK', 16, 10], ['MRK', 16, 11], ['MRK', 16, 12], ['MRK', 16, 13], ['MRK', 16, 14],
|
|
84
|
+
['MRK', 16, 15], ['MRK', 16, 16], ['MRK', 16, 17], ['MRK', 16, 18], ['MRK', 16, 19], ['MRK', 16, 20],
|
|
85
|
+
// ['LUK', 4, 8b],
|
|
86
|
+
// ['LUK', 9, 55], ['LUK', 9, 56],
|
|
87
|
+
['LUK', 17, 36],
|
|
88
|
+
['LUK', 22, 43], // ?
|
|
89
|
+
['LUK', 22, 44], // ?
|
|
90
|
+
['LUK', 23, 17],
|
|
91
|
+
['JHN', 5, 3], ['JHN', 5, 4],
|
|
92
|
+
['JHN', 7, 53], // ?
|
|
93
|
+
['JHN', 8, 1], // ?
|
|
94
|
+
['ACT', 8, 37],
|
|
95
|
+
// ['ACT', 9, 5], ['ACT', 9, 6],
|
|
96
|
+
// ['ACT', 13, 42],
|
|
97
|
+
['ACT', 15, 34],
|
|
98
|
+
// ['ACT', 23, 9b],
|
|
99
|
+
['ACT', 24, 6], ['ACT', 24, 7], ['ACT', 24, 8],
|
|
100
|
+
['ACT', 28, 29],
|
|
101
|
+
['ROM', 16, 24],
|
|
102
|
+
['2CO', 13, 14], // ?
|
|
103
|
+
['1JN', 5, 7], ['1JN', 5, 8],
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
const extraBookList = ['FRT', 'BAK'];
|
|
108
|
+
export const isValidBookID = (bookId) => {
|
|
109
|
+
return bookId.toLowerCase() in BibleBookData || extraBookList.includes(bookId);
|
|
110
|
+
}
|
|
111
|
+
export const isOptionalValidBookID = (bookId) => {
|
|
112
|
+
return !bookId || bookId.toLowerCase() in BibleBookData || extraBookList.includes(bookId);
|
|
113
|
+
}
|
|
114
|
+
export const isExtraBookID = (bookId) => {
|
|
115
|
+
return extraBookList.includes(bookId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
export const usfmNumberName = (bookId) => {
|
|
120
|
+
try { return BibleBookData[bookId.toLowerCase()].usfm; }
|
|
121
|
+
catch (err) { throw new Error(`usfmNumberName() given invalid bookId: '${bookId}'`); }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const chaptersInBook = (bookId) => {
|
|
125
|
+
// parameterAssert(bookId.toLowerCase() !== 'obs', `chaptersInBook shouldn’t be passed '${bookId}'`);
|
|
126
|
+
let chapters;
|
|
127
|
+
try {
|
|
128
|
+
chapters = BibleBookData[bookId.toLowerCase()].chapters;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
throw new Error(`chaptersInBook() given invalid bookId: '${bookId}'`);
|
|
131
|
+
}
|
|
132
|
+
if (chapters === undefined) {
|
|
133
|
+
throw new Error(`chaptersInBook(): Invalid bookId: '${bookId}'`);
|
|
134
|
+
}
|
|
135
|
+
return chapters;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const isOneChapterBook = (bookId) => {
|
|
139
|
+
return chaptersInBook(bookId).length === 1;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const versesInChapter = (bookId, chapter) => {
|
|
143
|
+
// parameterAssert(bookId.toLowerCase() !== 'obs', `versesInChapter shouldn’t be passed '${bookId}'`);
|
|
144
|
+
const verses = chaptersInBook(bookId)[chapter - 1];
|
|
145
|
+
if (verses === undefined) {
|
|
146
|
+
throw new Error(`versesInChapter(${bookId}) given invalid chapter: ${chapter}`);
|
|
147
|
+
}
|
|
148
|
+
return verses;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const testament = (bookId) => {
|
|
152
|
+
const _testament = BibleBookData[bookId.toLowerCase()].testament;
|
|
153
|
+
return _testament;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export function getEnglishBookName(bookId) {
|
|
157
|
+
return BibleBookData[bookId.toLowerCase()].title;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function isGoodEnglishBookName(givenBookName) {
|
|
161
|
+
// debugLog(`isGoodEnglishBookName(${givenBookName})…`);
|
|
162
|
+
const partialMatches = [];
|
|
163
|
+
const givenBookNameLower = givenBookName.toLowerCase();
|
|
164
|
+
for (const bk in BibleBookData) {
|
|
165
|
+
const thisBookName = BibleBookData[bk].title;
|
|
166
|
+
// debugLog("thisBookName", thisBookName);
|
|
167
|
+
if (thisBookName === givenBookName) return true;
|
|
168
|
+
const thisBookNameLower = thisBookName.toLowerCase();
|
|
169
|
+
if (thisBookNameLower === givenBookNameLower) return 1;
|
|
170
|
+
if (thisBookNameLower.startsWith(givenBookNameLower)) partialMatches.push(thisBookName);
|
|
171
|
+
}
|
|
172
|
+
if (partialMatches.length === 1) return 2; // We got an unambiguous partial match, e.g., Gen for Genesis
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function isOftenMissing(bookID, C, V) {
|
|
177
|
+
function matchBCV(entry) { return entry[0] === bookID && entry[1] === C && entry[2] === V; }
|
|
178
|
+
|
|
179
|
+
return oftenMissingBCVList.find(matchBCV) !== undefined;
|
|
180
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Main module for twl-generator
|
|
2
|
+
import { generateTWTerms } from './utils/zipProcessor.js';
|
|
3
|
+
import { processUsfmForBook, parseUsfmToVerses } from './utils/usfm-alignment-remover.js';
|
|
4
|
+
import { generateTWLMatches } from './utils/twl-matcher.js';
|
|
5
|
+
|
|
6
|
+
export { generateTWTerms, processUsfmForBook };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Main function that processes both TW articles and USFM file
|
|
10
|
+
* @param {string} book - The book identifier (optional if usfmContent is provided)
|
|
11
|
+
* @param {string} usfmContent - Optional USFM content to process instead of fetching
|
|
12
|
+
* @return {Promise<string>} - TSV string
|
|
13
|
+
*/
|
|
14
|
+
export async function generateTWLWithUsfm(book, usfmContent = null) {
|
|
15
|
+
// Generate TW terms (with caching)
|
|
16
|
+
const terms = await generateTWTerms(book || 'gen'); // Use 'gen' as fallback for terms generation
|
|
17
|
+
|
|
18
|
+
let verses;
|
|
19
|
+
if (usfmContent) {
|
|
20
|
+
// Parse provided USFM content
|
|
21
|
+
verses = parseUsfmToVerses(usfmContent);
|
|
22
|
+
} else {
|
|
23
|
+
// Fetch USFM from git.door43.org
|
|
24
|
+
if (!book) throw new Error('Book parameter required when no USFM content provided');
|
|
25
|
+
verses = await processUsfmForBook(book);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Generate TWL matches and return TSV
|
|
29
|
+
const tsv = generateTWLMatches(terms, verses);
|
|
30
|
+
return tsv;
|
|
31
|
+
}
|