/
gatewayLanguageHelpers.js
500 lines (475 loc) · 19 KB
/
gatewayLanguageHelpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
import fs from 'fs-extra';
import path from "path-extra";
import _ from 'lodash';
import {getAlignedText} from 'tc-ui-toolkit';
import {getLanguageByCodeSelection, sortByNamesCaseInsensitive} from "./LanguageHelpers";
import * as ResourcesHelpers from "./ResourcesHelpers";
import * as BibleHelpers from "./bibleHelpers";
import {getSelectedToolName, getToolGatewayLanguage} from "../selectors";
import ResourceAPI from "./ResourceAPI";
// constants
import {
TRANSLATION_ACADEMY,
TRANSLATION_HELPS,
TRANSLATION_NOTES,
TRANSLATION_WORDS,
USER_RESOURCES_PATH,
WORD_ALIGNMENT,
LEXICONS,
UGL_LEXICON,
UHL_LEXICON,
} from '../common/constants';
/**
*
* @param {Object} state - current state
* @param {Object} contextId - optional contextId to use, otherwise uses current
* @return {{gatewayLanguageCode: *, gatewayLanguageQuote: *}}
*/
export const getGatewayLanguageCodeAndQuote = (state, contextId = null) => {
const toolName = getSelectedToolName(state);
const gatewayLanguageCode = getToolGatewayLanguage(state, toolName);
const {toolsSelectedGLs} = state.projectDetailsReducer.manifest;
const gatewayLanguageQuote = getAlignedGLText(
toolsSelectedGLs,
contextId || state.contextIdReducer.contextId,
state.resourcesReducer.bibles,
toolName
);
return {
gatewayLanguageCode,
gatewayLanguageQuote
};
};
/**
* lookup required helps for tool to be supported Gateway Languages
* @param toolName
* @return {{gl: {alignedBookRequired: Boolean, minimumCheckingLevel: Number, helpsChecks: Array.<Object>},
* ol: {alignedBookRequired: Boolean, minimumCheckingLevel: Number, helpsChecks: Array.<Object>}}}
*/
export function getGlRequirementsForTool(toolName) {
const requirements = { // init to default values
gl: {
alignedBookRequired: false,
minimumCheckingLevel: 3,
helpsChecks: []
},
ol: {
alignedBookRequired: false,
minimumCheckingLevel: 2,
helpsChecks: []
}
};
switch (toolName) {
case WORD_ALIGNMENT:
requirements.gl.minimumCheckingLevel = 3;
requirements.gl.helpsChecks = [
{
path: path.join(LEXICONS),
}
];
break;
case TRANSLATION_WORDS:
requirements.gl.alignedBookRequired = true;
requirements.gl.minimumCheckingLevel = 3;
requirements.gl.helpsChecks = [
{
path: path.join(TRANSLATION_HELPS, TRANSLATION_WORDS),
subpath: 'articles',
minimumCheckingLevel: 2
}
];
requirements.ol.helpsChecks = [
{
path: path.join(TRANSLATION_HELPS, TRANSLATION_WORDS),
subpath: path.join('groups', '${bookID}')
}
];
break;
case TRANSLATION_NOTES:
requirements.gl.alignedBookRequired = true;
requirements.gl.helpsChecks = [
{
path: path.join(TRANSLATION_HELPS, TRANSLATION_ACADEMY)
},
{
path: path.join(TRANSLATION_HELPS, TRANSLATION_NOTES),
subpath: path.join('groups', '${bookID}')
}
];
break;
}
return requirements;
}
/**
* Returns an alphabetical list of Gateway Languages. This list is determined by iterating through each language
* in resources and then each bible in that language to make sure that at least one bible is supported
* in that language.
* See getValidGatewayBibles() for rules that determine if a bible can be used as gateway source.
*
* @param {String|null} bookId - optionally filter on book
* @param {String} toolName
* @return {Object} set of supported languages
*/
export function getGatewayLanguageList(bookId = null, toolName = null) {
const glRequirements = getGlRequirementsForTool(toolName);
const languageBookData = getSupportedGatewayLanguageResourcesList(bookId, glRequirements, toolName);
const supportedLanguageCodes = Object.keys(languageBookData);
const supportedLanguages = supportedLanguageCodes.map(code => {
let lang = getLanguageByCodeSelection(code);
if (lang) {
lang = _.cloneDeep(lang); // make duplicate before modifying
const bookData = languageBookData[code];
lang.default_literal = bookData.default_literal;
lang.bibles = bookData.bibles;
lang.lc = lang.code; // UI expects language code in lc
}
return lang;
});
return sortByNamesCaseInsensitive(supportedLanguages.filter(lang => lang));
}
/**
* look for alignments in book. Check first chapter and iterate through verseObjects.
* Return true when first alignment is found.
* @param {Array} chapters
* @param {String} bookPath
* @return {*}
*/
function seeIfBookHasAlignments(chapters, bookPath) {
let file = chapters.sort().find(item => (item.indexOf('1.') >= 0));
if (!file) { // if couldn't find chapter one, fall back to first file found
file = chapters[0];
}
if (file) {
try {
const chapter1 = fs.readJSONSync(path.join(bookPath, file));
for (let verseNum of Object.keys(chapter1)) {
const verse = chapter1[verseNum];
if (verse && verse.verseObjects) {
for (let vo of verse.verseObjects) {
if (vo.tag === "zaln") { // verse has alignments
return true;
}
}
}
}
} catch (e) {
// read or parse error
}
}
return false;
}
/**
* verify that resource is present and meets requirements that it has manifest.json, optionally meets checking level,
* and optionally has alignment data
*
* @param {String} resourcePath
* @param {String} bookId
* @param {int} minCheckingLevel - checked if non-zero
* @param {Boolean} needsAlignmentData - if true, we ensure that resource contains alignment data
* @return {Boolean}
*/
function isValidResource(resourcePath, bookId, minCheckingLevel, needsAlignmentData = false) {
const ultManifestPath = path.join(resourcePath, 'manifest.json');
const bookPath = path.join(resourcePath, bookId);
let validResource = fs.pathExistsSync(ultManifestPath) && fs.pathExistsSync(bookPath);
if (validResource) {
let files = ResourcesHelpers.getFilesInResourcePath(bookPath, '.json');
validResource = files && files.length; // if book has files in it
if (validResource && minCheckingLevel) { // should we validate checking level
const manifest = ResourcesHelpers.getBibleManifest(resourcePath, bookId);
validResource = manifest && manifest.checking && manifest.checking.checking_level;
validResource = validResource && (manifest.checking.checking_level >= minCheckingLevel);
}
if (validResource && needsAlignmentData) { // shoud we validate alignment data
validResource = seeIfBookHasAlignments(files, bookPath, validResource);
}
}
return validResource;
}
/**
* does some basic validation checking that langPath+subPath is a resource folder and returns path to latest
* resource
*
* @param {String} langPath
* @param {String} subpath
* @return {String} resource version path
*/
function getValidResourcePath(langPath, subpath) {
const validPath = ResourceAPI.getLatestVersion(path.join(langPath, subpath));
if (validPath) {
const subFolders = ResourcesHelpers.getFoldersInResourceFolder(validPath);
if (subFolders && subFolders.length) { // make sure it has subfolders
return validPath;
}
}
return null;
}
/**
* get path for OL of book
* @param {String} bookId - book to look up
* @return {String}
*/
export function getOlBookPath(bookId) {
const {languageId, bibleId} = BibleHelpers.getOrigLangforBook(bookId);
const originalSubPath = `${languageId}/bibles/${bibleId}`;
const origPath = getValidResourcePath(USER_RESOURCES_PATH, originalSubPath);
return origPath;
}
/**
* test to make sure book has valid OL
* @param {String} bookId - book to look up
* @param {Number} minimumCheckingLevel
* @return {Boolean}
*/
export function hasValidOL(bookId, minimumCheckingLevel = 0) {
const origPath = getOlBookPath(bookId);
const isValidOrig = origPath && isValidResource(origPath, bookId, minimumCheckingLevel);
return isValidOrig;
}
/**
* Returns a list of Gateway Languages bibles supported for book. This list is determined by iterating through each
* bible in the language to make sure that at least one bible is supported.
* Supported books meet the following requirements
*
* for WA tool (no helpsChecks):
* - supported bible:
* - contains the book, and which must have alignments
* - bible has a manifest
* - the book must also be present in the Original Language (such as el-x-koine).
* for other tools (with helpsChecks) have the requirements above plus:
* - the Original Language for book must be at least checking level 2 (in manifest).
* - the aligned bible in the gateway Language:
* - must be at least checking level 3 (in manifest).
* - all folder subpaths in helpsChecks must be present
*
* @param {String} toolName - name of current tool
* @param {String} langCode - language to check
* @param {string} bookId - optionally filter on book
* @param {Object} biblesLoaded - bibles already loaded in the state
* @return {Array} valid bibles that can be used for Gateway language
*/
export function getValidGatewayBiblesForTool(toolName, langCode, bookId, biblesLoaded = {}) {
const glRequirements = getGlRequirementsForTool(toolName);
const validBibles = getValidGatewayBibles(langCode, bookId, glRequirements, biblesLoaded);
return validBibles;
}
/**
* validate that folder exists
* @param {String} folderPath
* @return {boolean}
*/
function isDirectory(folderPath) {
return fs.existsSync(folderPath) && fs.lstatSync(folderPath).isDirectory();
}
/**
*
* @param {Array.<Object>} helpsChecks - list of helps to check
* @param {String} languagePath
* @param {String} bookID
* @return {boolean}
*/
function hasValidHelps(helpsChecks, languagePath, bookID = '') {
let isBibleValidSource = true;
const checkingHelps = helpsChecks && helpsChecks.length;
if (checkingHelps) { // if no resource checking given, we add empty check
isBibleValidSource = true;
for (let helpsCheck of helpsChecks) {
let helpValid = false;
const latestVersionPath = ResourceAPI.getLatestVersion(path.join(languagePath, helpsCheck.path));
if (latestVersionPath) {
const subFolders = ResourcesHelpers.getFoldersInResourceFolder(latestVersionPath);
if (subFolders && subFolders.length) { // make sure it has subfolders
helpValid = subFolders.find(subFolder => {
const subFolderPath = path.join(latestVersionPath, subFolder);
if (isDirectory(subFolderPath)) {
let checkPath, subpath = helpsCheck.subpath || '';
checkPath = path.join(subFolderPath, subpath.replace('${bookID}', bookID));
if (isDirectory(checkPath)) {
const validFile = fs.readdirSync(checkPath).find(file => {
const ext = path.parse(file).ext;
return ((ext === '.json') || (ext === '.md'));
});
return validFile;
}
}
return false;
});
}
if (helpsCheck.minimumCheckingLevel) {
let checkingLevel = -1;
const manifestPath = path.join(latestVersionPath, 'manifest.json');
if (fs.existsSync(manifestPath)) {
const manifest = fs.readJsonSync(manifestPath);
checkingLevel = (manifest && manifest.checking && manifest.checking.checking_level) || -1;
}
const passedCheckingLevel = (checkingLevel >= helpsCheck.minimumCheckingLevel);
helpValid = helpValid && passedCheckingLevel;
}
} else if (helpsCheck.path.includes('lexicons')) {
const lexiconId = BibleHelpers.isNewTestament(bookID) ? UGL_LEXICON : UHL_LEXICON;
const lexiconsFolderPath = path.join(languagePath, helpsCheck.path, lexiconId);
if (fs.existsSync(lexiconsFolderPath)) {
const lexiconLatestVersionPath = ResourceAPI.getLatestVersion(path.join(languagePath, helpsCheck.path, lexiconId));
if (fs.existsSync(path.join(lexiconLatestVersionPath, 'content'))) {
helpValid = true;
}
}
}
isBibleValidSource = isBibleValidSource && helpValid;
}
}
return isBibleValidSource;
}
/**
* Returns a list of Gateway Languages bibles supported for book. This list is determined by iterating through each
* bible in the language to make sure that at least one bible is supported.
* Supported books meet the following requirements
*
* for WA tool (no helpsChecks):
* - supported bible:
* - contains the book, and which must have alignments
* - bible has a manifest
* - the book must also be present in the Original Language (such as el-x-koine).
* for other tools (with helpsChecks) have the requirements above plus:
* - the Original Language for book must be at least checking level 2 (in manifest).
* - the aligned bible in the gateway Language:
* - must be at least checking level 3 (in manifest).
* - all folder subpaths in helpsChecks must be present
*
* @param {String} langCode - language to check
* @param {string} bookId - optionally filter on book
* @param {Object} glRequirements - helpsPaths - see getGlRequirementsForTool() jsDocs for format
* @param {Object} biblesLoaded - bibles already loaded in the state
* @return {Array} valid bibles that can be used for Gateway language
*/
export function getValidGatewayBibles(langCode, bookId, glRequirements = {}, biblesLoaded = {}) {
const languagePath = path.join(USER_RESOURCES_PATH, langCode);
const biblesPath = path.join(languagePath, 'bibles');
let bibles = fs.existsSync(biblesPath) ? fs.readdirSync(biblesPath) : [];
bibles = bibles.filter(bibleId => {
return !(biblesLoaded[langCode] && biblesLoaded[langCode][bibleId]);
});
const validBibles = bibles.filter(bible => {
if (!fs.lstatSync(path.join(biblesPath, bible)).isDirectory()) { // verify it's a valid directory
return false;
}
let isBibleValidSource = false;
let biblePath = getValidResourcePath(biblesPath, bible);
if (biblePath) {
isBibleValidSource = hasValidHelps(glRequirements.gl.helpsChecks, languagePath, bookId);
if (isBibleValidSource) {
if (bookId) { // if filtering by book
const isValidOrig = hasValidOL(bookId, glRequirements.ol.minimumCheckingLevel); // make sure we have an OL for the book
isBibleValidSource = isBibleValidSource && isValidOrig;
if (glRequirements.ol.helpsChecks && glRequirements.ol.helpsChecks.length) {
const olBook = BibleHelpers.getOrigLangforBook(bookId);
const olPath = path.join(USER_RESOURCES_PATH, olBook.languageId);
isBibleValidSource = isBibleValidSource && hasValidHelps(glRequirements.ol.helpsChecks, olPath, bookId);
}
// make sure resource for book is present and has the right checking level
const isValidUlt = biblePath && isValidResource(biblePath, bookId,
glRequirements.gl.minimumCheckingLevel, glRequirements.gl.alignedBookRequired);
isBibleValidSource = isBibleValidSource && isValidUlt;
}
}
}
return isBibleValidSource;
});
return validBibles;
}
/**
* Returns a list of Gateway Languages supported for book. This list is determined by iterating through each language
* in resources and then each bible in that language to make sure that at least one bible is supported
* in that language.
* See getValidGatewayBibles() for rules that determine if a bible can be used as gateway source.
*
* @param {String|null} bookId - optionally filter on book
* @param {Object} glRequirements - helpsPaths - see getGlRequirementsForTool() jsDocs for format
* @param {String} toolName - tool name.
* @return {Object} set of supported languages and their supported bibles
*/
export function getSupportedGatewayLanguageResourcesList(bookId = null, glRequirements = {}, toolName) {
const allLanguages = ResourcesHelpers.getAllLanguageIdsFromResourceFolder(true) || [];
const filteredLanguages = {};
for (let language of allLanguages) {
const validBibles = getValidGatewayBibles(language, bookId, glRequirements);
if (validBibles && validBibles.length) {
const default_literal = validBibles[0];
filteredLanguages[language] = {
default_literal,
bibles: validBibles
};
}
}
if (!filteredLanguages['en'] && toolName === WORD_ALIGNMENT && !Object.keys(filteredLanguages).length) { // TODO: this is a temporary fix to be removed later
filteredLanguages['en'] = {
default_literal: 'ult',
bibles: ['ult']
};
}
return filteredLanguages;
}
/**
* gets the quote as a string array
* @param {Object} contextId
* @return {Array}
*/
export function getQuoteAsArray(contextId) {
let quoteArray = [];
if (Array.isArray(contextId.quote)) {
for (let i = 0, l = contextId.quote.length; i < l; i++) {
const wordItem = contextId.quote[i];
quoteArray.push(wordItem.word);
}
} else {
quoteArray = contextId.quote.split(' ');
}
return quoteArray;
}
/**
* get the selected text from the GL resource for this context
* @param {*} toolsSelectedGLs
* @param {*} contextId
* @param {*} bibles - list of resources
* @param {*} currentToolName - such as translationWords
*/
export function getAlignedGLText(toolsSelectedGLs, contextId, bibles, currentToolName) {
const selectedGL = toolsSelectedGLs[currentToolName];
if (!contextId.quote || !bibles || !bibles[selectedGL] || !Object.keys(bibles[selectedGL]).length)
return contextId.quote;
const sortedBibleIds = Object.keys(bibles[selectedGL]).sort(bibleIdSort);
for (let i = 0; i < sortedBibleIds.length; ++i) {
const bible = bibles[selectedGL][sortedBibleIds[i]];
const alignedText = getAlignedTextFromBible(contextId, bible);
if (alignedText) {
return alignedText;
}
}
return contextId.quote;
}
/**
* gets the aligned GL text from the given bible
* @param {object} contextId
* @param {object} bible
* @returns {string}
*/
export function getAlignedTextFromBible(contextId, bible) {
if (bible && contextId && contextId.reference &&
bible[contextId.reference.chapter] && bible[contextId.reference.chapter][contextId.reference.verse] &&
bible[contextId.reference.chapter][contextId.reference.verse].verseObjects) {
const verseObjects = bible[contextId.reference.chapter][contextId.reference.verse].verseObjects;
return getAlignedText(verseObjects, contextId.quote, contextId.occurrence);
}
}
/**
* Return book code with highest precidence
* @param {*} a - First book code of 2
* @param {*} b - second book code
*/
export function bibleIdSort(a, b) {
const biblePrecedence = ['udb', 'ust', 'ulb', 'ult', 'irv']; // these should come first in this order if more than one aligned Bible, from least to greatest
if (biblePrecedence.indexOf(a) == biblePrecedence.indexOf(b))
return (a < b ? -1 : a > b ? 1 : 0);
else
return biblePrecedence.indexOf(b) - biblePrecedence.indexOf(a); // this plays off the fact other Bible IDs will be -1
}