segmenters.ts 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. const wordRe = /['\-.\p{L}]+/gu
  2. const wordLikeRe = /\p{L}/u // must contain at least one "letter" to be a word
  3. const characterRe = /\S/gu
  4. type SegmentDataLike = {
  5. segment: string
  6. isWordLike?: boolean
  7. }
  8. type SegmenterLike = {
  9. segment(input: string): {
  10. [Symbol.iterator](): IterableIterator<SegmentDataLike>
  11. }
  12. }
  13. export type Segmenters = {
  14. word: SegmenterLike
  15. character: SegmenterLike
  16. }
  17. export const createSegmenters = (locale?: string): Segmenters => {
  18. if (!Intl.Segmenter) {
  19. return genericSegmenters
  20. }
  21. try {
  22. return {
  23. word: new Intl.Segmenter(locale, {
  24. granularity: 'word', // TODO: count hyphenated words as a single word
  25. }),
  26. character: new Intl.Segmenter(locale, {
  27. granularity: 'grapheme',
  28. }),
  29. }
  30. } catch {
  31. return genericSegmenters
  32. }
  33. }
  34. const genericSegmenters = {
  35. word: {
  36. segment(input: string) {
  37. const segments: SegmentDataLike[] = []
  38. for (const match of input.matchAll(wordRe)) {
  39. segments.push({
  40. segment: match[0],
  41. isWordLike: wordLikeRe.test(match[0]),
  42. })
  43. }
  44. return segments
  45. },
  46. },
  47. character: {
  48. segment(input: string) {
  49. const segments: SegmentDataLike[] = []
  50. for (const match of input.matchAll(characterRe)) {
  51. segments.push({
  52. segment: match[0],
  53. })
  54. }
  55. return segments
  56. },
  57. },
  58. }