ipa-hangul 1.2.2 → 1.3.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/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +63 -2
- package/dist/index.mjs +63 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
interface IpaToHangulOptions {
|
|
14
14
|
markStress?: 'markdown' | 'html';
|
|
15
|
+
/**
|
|
16
|
+
* When true, trailing consonants move to next syllable if it starts with a vowel.
|
|
17
|
+
* This produces more natural Korean loan word pronunciation.
|
|
18
|
+
* e.g., "tɪti" → "티티" instead of "팉이"
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
preferOnset?: boolean;
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
24
|
* Convert IPA notation to Korean Hangul pronunciation
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
interface IpaToHangulOptions {
|
|
14
14
|
markStress?: 'markdown' | 'html';
|
|
15
|
+
/**
|
|
16
|
+
* When true, trailing consonants move to next syllable if it starts with a vowel.
|
|
17
|
+
* This produces more natural Korean loan word pronunciation.
|
|
18
|
+
* e.g., "tɪti" → "티티" instead of "팉이"
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
preferOnset?: boolean;
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
24
|
* Convert IPA notation to Korean Hangul pronunciation
|
package/dist/index.js
CHANGED
|
@@ -262,7 +262,56 @@ function preprocessIPA(ipa) {
|
|
|
262
262
|
function hasIPAVowel(text) {
|
|
263
263
|
return /[iɪeɛæɑɒɔʌəɜɝʊuoa]/.test(text);
|
|
264
264
|
}
|
|
265
|
-
function
|
|
265
|
+
function getTrailingConsonants(text, preferOnset = false) {
|
|
266
|
+
let i = text.length;
|
|
267
|
+
while (i > 0) {
|
|
268
|
+
if (i >= 2) {
|
|
269
|
+
const twoChar = text.substring(i - 2, i);
|
|
270
|
+
if (CONSONANT_TO_CHOSEONG[twoChar] || CONSONANT_TO_JAMO[twoChar]) {
|
|
271
|
+
i -= 2;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const oneChar = text[i - 1];
|
|
276
|
+
if (CONSONANT_TO_CHOSEONG[oneChar] || CONSONANT_TO_JAMO[oneChar]) {
|
|
277
|
+
i--;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
const beforeConsonants = text.substring(0, i);
|
|
283
|
+
const allTrailing = text.substring(i);
|
|
284
|
+
if (!allTrailing) {
|
|
285
|
+
return { before: text, trailing: "" };
|
|
286
|
+
}
|
|
287
|
+
if (preferOnset) {
|
|
288
|
+
return {
|
|
289
|
+
before: beforeConsonants,
|
|
290
|
+
trailing: allTrailing
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
let keepUntil = 0;
|
|
294
|
+
if (allTrailing.length >= 2) {
|
|
295
|
+
const twoChar = allTrailing.substring(0, 2);
|
|
296
|
+
if (CONSONANT_TO_JONGSEONG[twoChar] !== void 0) {
|
|
297
|
+
keepUntil = 2;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (keepUntil === 0 && allTrailing.length >= 1) {
|
|
301
|
+
const oneChar = allTrailing[0];
|
|
302
|
+
if (CONSONANT_TO_JONGSEONG[oneChar] !== void 0) {
|
|
303
|
+
keepUntil = 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
before: beforeConsonants + allTrailing.substring(0, keepUntil),
|
|
308
|
+
trailing: allTrailing.substring(keepUntil)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function startsWithVowel(text) {
|
|
312
|
+
return matchVowel(text, 0) !== null;
|
|
313
|
+
}
|
|
314
|
+
function parseSyllables(text, preferOnset = true) {
|
|
266
315
|
const syllables = [];
|
|
267
316
|
const parts = text.split(".");
|
|
268
317
|
for (const part of parts) {
|
|
@@ -285,6 +334,17 @@ function parseSyllables(text) {
|
|
|
285
334
|
}
|
|
286
335
|
merged.push(curr);
|
|
287
336
|
}
|
|
337
|
+
for (let i = 0; i < merged.length - 1; i++) {
|
|
338
|
+
const curr = merged[i];
|
|
339
|
+
const next = merged[i + 1];
|
|
340
|
+
if (startsWithVowel(next.text)) {
|
|
341
|
+
const { before, trailing } = getTrailingConsonants(curr.text, preferOnset);
|
|
342
|
+
if (trailing && before) {
|
|
343
|
+
curr.text = before;
|
|
344
|
+
next.text = trailing + next.text;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
288
348
|
return merged;
|
|
289
349
|
}
|
|
290
350
|
function splitByLongVowel(text) {
|
|
@@ -458,9 +518,10 @@ function applyStressMarker(hangul, stress, format) {
|
|
|
458
518
|
}
|
|
459
519
|
function ipaToHangul(ipa, options) {
|
|
460
520
|
if (!ipa) return "";
|
|
521
|
+
const preferOnset = options?.preferOnset !== false;
|
|
461
522
|
const cleaned = preprocessIPA(ipa);
|
|
462
523
|
if (!cleaned) return "";
|
|
463
|
-
const syllables = parseSyllables(cleaned);
|
|
524
|
+
const syllables = parseSyllables(cleaned, preferOnset);
|
|
464
525
|
const results = [];
|
|
465
526
|
for (const syllable of syllables) {
|
|
466
527
|
const segments = splitByLongVowel(syllable.text);
|
package/dist/index.mjs
CHANGED
|
@@ -238,7 +238,56 @@ function preprocessIPA(ipa) {
|
|
|
238
238
|
function hasIPAVowel(text) {
|
|
239
239
|
return /[iɪeɛæɑɒɔʌəɜɝʊuoa]/.test(text);
|
|
240
240
|
}
|
|
241
|
-
function
|
|
241
|
+
function getTrailingConsonants(text, preferOnset = false) {
|
|
242
|
+
let i = text.length;
|
|
243
|
+
while (i > 0) {
|
|
244
|
+
if (i >= 2) {
|
|
245
|
+
const twoChar = text.substring(i - 2, i);
|
|
246
|
+
if (CONSONANT_TO_CHOSEONG[twoChar] || CONSONANT_TO_JAMO[twoChar]) {
|
|
247
|
+
i -= 2;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const oneChar = text[i - 1];
|
|
252
|
+
if (CONSONANT_TO_CHOSEONG[oneChar] || CONSONANT_TO_JAMO[oneChar]) {
|
|
253
|
+
i--;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const beforeConsonants = text.substring(0, i);
|
|
259
|
+
const allTrailing = text.substring(i);
|
|
260
|
+
if (!allTrailing) {
|
|
261
|
+
return { before: text, trailing: "" };
|
|
262
|
+
}
|
|
263
|
+
if (preferOnset) {
|
|
264
|
+
return {
|
|
265
|
+
before: beforeConsonants,
|
|
266
|
+
trailing: allTrailing
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
let keepUntil = 0;
|
|
270
|
+
if (allTrailing.length >= 2) {
|
|
271
|
+
const twoChar = allTrailing.substring(0, 2);
|
|
272
|
+
if (CONSONANT_TO_JONGSEONG[twoChar] !== void 0) {
|
|
273
|
+
keepUntil = 2;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (keepUntil === 0 && allTrailing.length >= 1) {
|
|
277
|
+
const oneChar = allTrailing[0];
|
|
278
|
+
if (CONSONANT_TO_JONGSEONG[oneChar] !== void 0) {
|
|
279
|
+
keepUntil = 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
before: beforeConsonants + allTrailing.substring(0, keepUntil),
|
|
284
|
+
trailing: allTrailing.substring(keepUntil)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function startsWithVowel(text) {
|
|
288
|
+
return matchVowel(text, 0) !== null;
|
|
289
|
+
}
|
|
290
|
+
function parseSyllables(text, preferOnset = true) {
|
|
242
291
|
const syllables = [];
|
|
243
292
|
const parts = text.split(".");
|
|
244
293
|
for (const part of parts) {
|
|
@@ -261,6 +310,17 @@ function parseSyllables(text) {
|
|
|
261
310
|
}
|
|
262
311
|
merged.push(curr);
|
|
263
312
|
}
|
|
313
|
+
for (let i = 0; i < merged.length - 1; i++) {
|
|
314
|
+
const curr = merged[i];
|
|
315
|
+
const next = merged[i + 1];
|
|
316
|
+
if (startsWithVowel(next.text)) {
|
|
317
|
+
const { before, trailing } = getTrailingConsonants(curr.text, preferOnset);
|
|
318
|
+
if (trailing && before) {
|
|
319
|
+
curr.text = before;
|
|
320
|
+
next.text = trailing + next.text;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
264
324
|
return merged;
|
|
265
325
|
}
|
|
266
326
|
function splitByLongVowel(text) {
|
|
@@ -434,9 +494,10 @@ function applyStressMarker(hangul, stress, format) {
|
|
|
434
494
|
}
|
|
435
495
|
function ipaToHangul(ipa, options) {
|
|
436
496
|
if (!ipa) return "";
|
|
497
|
+
const preferOnset = options?.preferOnset !== false;
|
|
437
498
|
const cleaned = preprocessIPA(ipa);
|
|
438
499
|
if (!cleaned) return "";
|
|
439
|
-
const syllables = parseSyllables(cleaned);
|
|
500
|
+
const syllables = parseSyllables(cleaned, preferOnset);
|
|
440
501
|
const results = [];
|
|
441
502
|
for (const syllable of syllables) {
|
|
442
503
|
const segments = splitByLongVowel(syllable.text);
|