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 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
+ }