~repos /only-bible-app

#kotlin#android#ios

GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone https://git.pyrossh.dev/only-bible-app.git
Discussions: https://groups.google.com/g/rust-embed-devs

The only bible app you will ever need. No ads. No in-app purchases. No distractions.



scripts/addRedLettersJa.cjs



const fs = require("fs");
const path = require("path");
const kjvPath = path.join(__dirname, "files", "en_kjv.txt");
const jaPath = path.join(__dirname, "files", "ja.txt");
// Step 1: Find partial-red verse IDs from KJV
const kjvLines = fs.readFileSync(kjvPath, "utf-8").split("\n").filter(Boolean);
const partialRedIds = new Set();
const fullyRedIds = new Set();
for (const line of kjvLines) {
if (!line.includes("<red>")) continue;
const parts = line.split("|");
const text = parts.slice(6).join("|").replace(/^[¶\s]+/, "").trim();
const id = `${parts[1]}:${parts[2]}:${parts[3]}`;
if (/^<red>.*<\/red>$/.test(text)) {
fullyRedIds.add(id);
} else {
partialRedIds.add(id);
}
}
console.log(`KJV: ${fullyRedIds.size} fully-red, ${partialRedIds.size} partial-red`);
// Step 2: For partial-red verses, determine the pattern from KJV
// We need to know: does red text come at start, middle, or end?
const kjvPatterns = {};
for (const line of kjvLines) {
if (!line.includes("<red>")) continue;
const parts = line.split("|");
const text = parts.slice(6).join("|").replace(/^[¶\s]+/, "").trim();
const id = `${parts[1]}:${parts[2]}:${parts[3]}`;
if (!partialRedIds.has(id)) continue;
// Extract red and non-red segments
const segments = [];
let pos = 0;
const raw = text;
while (pos < raw.length) {
const redStart = raw.indexOf("<red>", pos);
if (redStart === -1) {
const rest = raw.substring(pos).trim();
if (rest) segments.push({ text: rest, red: false });
break;
}
if (redStart > pos) {
const before = raw.substring(pos, redStart).trim();
if (before) segments.push({ text: before, red: false });
}
const redEnd = raw.indexOf("</red>", redStart);
if (redEnd === -1) {
segments.push({ text: raw.substring(redStart + 5).trim(), red: true });
break;
}
const redText = raw.substring(redStart + 5, redEnd).trim();
if (redText) segments.push({ text: redText, red: true });
pos = redEnd + 6;
}
kjvPatterns[id] = segments;
}
// Step 3: Apply red tags to ja.txt using Japanese quotation marks
const jaLines = fs.readFileSync(jaPath, "utf-8").split("\n");
let applied = 0;
let skipped = 0;
// Track if we're in a multi-verse quote (red continues from previous verse)
// For verses without opening 「 that are partial-red, check if previous verse ended mid-quote
let inContinuedQuote = false;
const newLines = jaLines.map((line, lineIdx) => {
if (!line) return line;
const parts = line.split("|");
if (parts.length < 7) return line;
const id = `${parts[1]}:${parts[2]}:${parts[3]}`;
if (!partialRedIds.has(id)) {
// Reset continued quote tracking when we hit a non-red verse
if (!fullyRedIds.has(id)) {
inContinuedQuote = false;
}
return line;
}
// Already has red tags
const text = parts.slice(6).join("|");
if (text.includes("<red>")) return line;
const prefix = parts.slice(0, 6).join("|");
const pattern = kjvPatterns[id];
if (!pattern) return line;
// Check if text has Japanese quotation marks
const hasQuotes = text.includes("\u300c") || text.includes("\u300e");
if (!hasQuotes) {
// No quotes — this might be a continuation verse
// Check if the KJV pattern starts with red (continuation)
if (pattern[0] && pattern[0].red) {
// Find where the non-red part starts
// Look for 」 which would end the quote
const closeIdx = text.indexOf("\u300d");
if (closeIdx !== -1) {
const redPart = text.substring(0, closeIdx + 1);
const rest = text.substring(closeIdx + 1);
applied++;
return `${prefix}|<red>${redPart}</red>${rest}`;
}
// Check for 」 (double close)
const closeIdx2 = text.indexOf("\u300f");
if (closeIdx2 !== -1) {
const redPart = text.substring(0, closeIdx2 + 1);
const rest = text.substring(closeIdx2 + 1);
applied++;
return `${prefix}|<red>${redPart}</red>${rest}`;
}
// No closing quote found — entire verse might be red continuation
// ending with non-red narrator
// Check if KJV pattern ends with non-red
const lastSeg = pattern[pattern.length - 1];
if (lastSeg && !lastSeg.red) {
// This verse ends the quote without a closing bracket
// Skip — too risky to guess
skipped++;
return line;
}
}
skipped++;
return line;
}
// Has quotation marks — use them as boundaries
// Common patterns:
// 1. narrator「Jesus words」 => narrator<red>「Jesus words」</red>
// 2. narrator「Jesus words」narrator => narrator<red>「Jesus words」</red>narrator
// 3. 「Jesus words」narrator => <red>「Jesus words」</red>narrator
// 4. narrator「Jesus words」narrator「more words」 => multiple red sections
// Strategy: wrap each 「...」 pair in <red> tags if KJV says this verse has red content
// Use the KJV pattern to decide which quotes are red
// Count quote pairs in Japanese text
const quoteStarts = [];
const quoteEnds = [];
for (let i = 0; i < text.length; i++) {
if (text[i] === "\u300c") quoteStarts.push(i);
if (text[i] === "\u300d") quoteEnds.push(i);
}
// If simple single quote pair
if (quoteStarts.length === 1 && quoteEnds.length >= 1) {
const qStart = quoteStarts[0];
// Find the matching close — last 」 for safety
const qEnd = quoteEnds[quoteEnds.length - 1];
const before = text.substring(0, qStart);
const quoted = text.substring(qStart, qEnd + 1);
const after = text.substring(qEnd + 1);
applied++;
return `${prefix}|${before}<red>${quoted}</red>${after}`;
}
// Multiple quote pairs — wrap each one
if (quoteStarts.length > 1) {
// Build result by wrapping each 「...」 in <red>
let result = "";
let pos = 0;
let depth = 0;
let redStart = -1;
for (let i = 0; i < text.length; i++) {
if (text[i] === "\u300c") {
if (depth === 0) {
result += text.substring(pos, i);
redStart = i;
}
depth++;
} else if (text[i] === "\u300d") {
depth--;
if (depth === 0) {
result += "<red>" + text.substring(redStart, i + 1) + "</red>";
pos = i + 1;
}
}
}
// Append remaining
if (pos < text.length) {
result += text.substring(pos);
}
applied++;
return `${prefix}|${result}`;
}
// Edge case: has opening quote but no matching close (continues to next verse)
if (quoteStarts.length === 1 && quoteEnds.length === 0) {
const qStart = quoteStarts[0];
const before = text.substring(0, qStart);
const quoted = text.substring(qStart);
applied++;
return `${prefix}|${before}<red>${quoted}</red>`;
}
// Edge case: has closing quote but no opening (continuation from previous verse)
if (quoteStarts.length === 0 && quoteEnds.length >= 1) {
const qEnd = quoteEnds[quoteEnds.length - 1];
const quoted = text.substring(0, qEnd + 1);
const after = text.substring(qEnd + 1);
applied++;
return `${prefix}|<red>${quoted}</red>${after}`;
}
skipped++;
return line;
});
fs.writeFileSync(jaPath, newLines.join("\n"), "utf-8");
console.log(`Applied partial red to ${applied} verses, skipped ${skipped}`);
console.log(`Total red in ja.txt: ${newLines.filter(l => l.includes("<red>")).length}`);