Skip to content

Commit 4269876

Browse files
committed
fix(readability): import hyphen dependencies statically
1 parent 17fbcbe commit 4269876

File tree

1 file changed

+41
-57
lines changed

1 file changed

+41
-57
lines changed

src/readability/shared/multilingual-readability.js

Lines changed: 41 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@
1818

1919
import { syllable as syllableEn } from 'syllable';
2020

21+
// Static imports for hyphenation patterns (required for Lambda bundling)
22+
import hyphenDe from 'hyphen/de/index.js';
23+
import hyphenEs from 'hyphen/es/index.js';
24+
import hyphenIt from 'hyphen/it/index.js';
25+
import hyphenFr from 'hyphen/fr/index.js';
26+
import hyphenNl from 'hyphen/nl/index.js';
27+
28+
// Map of locale to statically imported hyphenation module
29+
const HYPHEN_MODULES = Object.freeze({
30+
de: hyphenDe,
31+
es: hyphenEs,
32+
it: hyphenIt,
33+
fr: hyphenFr,
34+
nl: hyphenNl,
35+
});
36+
2137
export const SUPPORTED_LANGUAGES = {
2238
eng: 'english',
2339
deu: 'german',
@@ -56,41 +72,34 @@ const COEFFS = Object.freeze({
5672
english: { A: 206.835, wps: 1.015, spw: 84.6 },
5773
});
5874

59-
// Optional: in-flight promise cache to dedupe concurrent analyze() calls
60-
const syllablePromiseCache = new Map(); // key -> Promise<number>
61-
6275
const clamp = (x) => Math.max(0, Math.min(100, x));
6376

64-
// --- Hyphenation loader (lazy) ---
65-
// cache promises to dedupe concurrent calls
66-
const hyphenatorCache = new Map(); // Map<string, Promise<Function|null>>
77+
// --- Hyphenation loader (using static imports) ---
78+
// cache to store resolved hyphenators
79+
const hyphenatorCache = new Map(); // Map<string, Function|null>
6780

68-
export async function getHyphenator(language) {
81+
export function getHyphenator(language) {
6982
const key = String(language).toLowerCase();
7083
const cached = hyphenatorCache.get(key);
71-
if (cached) return cached;
72-
73-
const loader = (async () => {
74-
const locale = LOCALE_MAP[key];
75-
if (!locale) {
76-
// eslint-disable-next-line no-console
77-
console.info(`[readability-suggest multilingual] 🔤 No hyphenation patterns available for language: ${language}`);
78-
return null;
79-
}
84+
if (cached !== undefined) return cached;
8085

86+
const locale = LOCALE_MAP[key];
87+
if (!locale) {
8188
// eslint-disable-next-line no-console
82-
console.info(`[readability-suggest multilingual] 📚 Loading hyphenation patterns for ${language} (locale: ${locale})`);
83-
const mod = await import(`hyphen/${locale}/index.js`);
84-
const hyphenate = mod?.default?.hyphenate;
85-
if (hyphenate) {
86-
// eslint-disable-next-line no-console
87-
console.info(`[readability-suggest multilingual] ✅ Successfully loaded hyphenation patterns for ${language}`);
88-
}
89-
return hyphenate;
90-
})();
89+
console.info(`[readability-suggest multilingual] 🔤 No hyphenation patterns available for language: ${language}`);
90+
hyphenatorCache.set(key, null);
91+
return null;
92+
}
9193

92-
hyphenatorCache.set(key, loader);
93-
return loader;
94+
const mod = HYPHEN_MODULES[locale];
95+
// Use hyphenateSync for better performance (no async overhead)
96+
const hyphenate = mod?.hyphenateSync;
97+
if (hyphenate) {
98+
// eslint-disable-next-line no-console
99+
console.info(`[readability-suggest multilingual] ✅ Loaded hyphenation patterns for ${language}`);
100+
}
101+
hyphenatorCache.set(key, hyphenate || null);
102+
return hyphenate || null;
94103
}
95104

96105
// --- Tokenization (streaming, locale-aware) ---
@@ -151,12 +160,12 @@ const defaultComplexThreshold = 3;
151160
* Count syllables for a single word in a given language.
152161
* EN uses "syllable"; others use hyphenation splits as proxy.
153162
*/
154-
async function countSyllablesWord(word, language) {
163+
function countSyllablesWord(word, language) {
155164
const lang = language.toLowerCase();
156165
if (lang === 'english') {
157166
return Math.max(1, syllableEn(word));
158167
}
159-
const hyphenate = await getHyphenator(lang);
168+
const hyphenate = getHyphenator(lang);
160169
if (!hyphenate) {
161170
// generic Unicode vowel group fallback
162171
const m = word.toLowerCase().match(/[aeiouyà-ɏ]+/giu);
@@ -172,7 +181,7 @@ async function countSyllablesWord(word, language) {
172181
}
173182
// Preserve inner apostrophes/dashes, remove other junk
174183
const cleaned = word.replace(/[^\p{L}\p{M}''-]/gu, '');
175-
const hyphenatedString = await hyphenate(cleaned);
184+
const hyphenatedString = hyphenate(cleaned);
176185
// Split by soft hyphen character (U+00AD) to get syllable parts
177186
const syllableParts = hyphenatedString ? hyphenatedString.split('\u00AD') : [word];
178187
const syllableCount = Math.max(1, syllableParts.length);
@@ -251,41 +260,16 @@ export async function analyzeReadability(text, language, opts = {}) {
251260
else entries.set(key, { word: w, count: 1 });
252261
}
253262

254-
// 2) Resolve syllables per unique word, using cache and deduped promises
255-
const toResolve = [];
263+
// 2) Resolve syllables per unique word, using cache
256264
for (const [key, { word }] of entries) {
257265
if (cache.get(key) == null) {
258-
let p = syllablePromiseCache.get(key);
259-
if (!p) {
260-
p = countSyllablesWord(word, lang).then((n) => {
261-
cache.set(key, n);
262-
syllablePromiseCache.delete(key); // keep cache small
263-
return n;
264-
});
265-
syllablePromiseCache.set(key, p);
266-
}
267-
toResolve.push(p);
266+
cache.set(key, countSyllablesWord(word, lang));
268267
}
269268
}
270-
if (toResolve.length) await Promise.all(toResolve);
271269

272270
// 3) Aggregate syllables/complex words using frequencies
273271
let syllableCount = 0;
274272
let complexWords = 0;
275-
// CRITICAL FIX: Check for corrupted cache entries and recalculate if needed
276-
const toRecalculate = [];
277-
for (const [key, { word }] of entries) {
278-
const s = cache.get(key);
279-
// If cache returns undefined/null or 0 syllables, need to recalculate
280-
if (s === undefined || s === null || s === 0) {
281-
toRecalculate.push(countSyllablesWord(word, lang).then((n) => {
282-
cache.set(key, n);
283-
return n;
284-
}));
285-
}
286-
}
287-
if (toRecalculate.length) await Promise.all(toRecalculate);
288-
289273
for (const [key, { count }] of entries) {
290274
const s = cache.get(key) ?? 0;
291275
syllableCount += s * count;

0 commit comments

Comments
 (0)