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 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 parseSyllables(text) {
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 parseSyllables(text) {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipa-hangul",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Convert IPA (International Phonetic Alphabet) pronunciation to Korean Hangul",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",