File size: 5,035 Bytes
9ab951d
6d8e2c3
069bab1
6d8e2c3
 
 
480d2cc
9ab951d
 
6d8e2c3
069bab1
 
 
4cce8ac
069bab1
 
 
f38c282
069bab1
 
 
 
 
 
 
 
d7a6833
069bab1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9827361
069bab1
 
 
 
 
 
 
 
 
b5cd05d
069bab1
e0bda2a
 
069bab1
 
 
 
9ab951d
069bab1
6d8e2c3
4cce8ac
 
069bab1
 
 
9ab951d
069bab1
9ab951d
 
6d8e2c3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
069bab1
 
 
 
 
 
 
 
 
 
6d8e2c3
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * Vietnamese text processing for both display and TTS.
 *
 * DESIGN: 
 * - Display text (chat & subtitles): numbers stay as digits for clean visual display
 * - TTS audio: numbers are converted to Vietnamese words for proper pronunciation
 * - Source links in greetings: rendered as clickable popup links
 */

// Vietnamese number words
const DIGITS = [
  "không", "một", "hai", "ba", "bốn", "năm", "sáu", "bảy", "tám", "chín",
];

function numberToVietnamese(n) {
  if (n === 0) return "không";
  if (n < 0) return "âm " + numberToVietnamese(-n);

  const units = ["", "nghìn", "triệu", "tỷ"];
  const groups = [];
  let temp = n;
  while (temp > 0) {
    groups.push(temp % 1000);
    temp = Math.floor(temp / 1000);
  }
  if (groups.length === 0) groups.push(0);

  const readGroup = (g) => {
    if (g === 0) return "";
    const h = Math.floor(g / 100);
    const r = g % 100;
    let s = "";
    if (h > 0) s += DIGITS[h] + " trăm ";
    else if (groups.length > 1) s += "không trăm ";
    if (r === 0) return s.trim();
    if (r < 10) {
      s += (h > 0 && r === 5) ? "lẻ năm" : "lẻ " + DIGITS[r];
    } else if (r < 20) {
      s += "mười" + (r === 10 ? "" : (r === 15 ? " lăm" : " " + DIGITS[r % 10]));
    } else {
      const t = Math.floor(r / 10);
      const o = r % 10;
      s += ["", "", "hai mươi", "ba mươi", "bốn mươi", "năm mươi",
            "sáu mươi", "bảy mươi", "tám mươi", "chín mươi"][t];
      if (o === 1) s += " mốt";
      else if (o === 5) s += " lăm";
      else if (o > 0) s += " " + DIGITS[o];
    }
    return s.replace(/\s+/g, " ").trim();
  };

  let result = "";
  for (let i = groups.length - 1; i >= 0; i--) {
    const g = groups[i];
    if (g === 0) continue;
    result += readGroup(g);
    if (i > 0) result += " " + units[i] + " ";
  }
  return result.replace(/\s+/g, " ").trim();
}

/**
 * Normalize Vietnamese text for display.
 * Numbers remain as digits for clean visual display.
 */
export function normalizeVietnameseText(text) {
  if (!text || typeof text !== "string") return text;
  return text.replace(/\s+/g, " ").trim();
}

/**
 * Check if text contains Vietnamese characters.
 */
export function containsVietnamese(text) {
  return /[àáạảãâầấậẩẫăằắặẳẵèéẹẻẽêềếệểễìíịỉĩòóọỏõôồốộổỗơờớợởỡùúụủũưừứựửữỳýỵỷỹđ]/i.test(text);
}

/**
 * Convert Arabic numerals to Vietnamese words for TTS.
 * Display texts keep the original digits.
 */
export function numbersToVietnameseWords(text) {
  if (!text || typeof text !== "string") return text;
  
  const units = ["", "nghìn", "triệu", "tỷ"];
  
  const numberToVietnameseLocal = (n) => {
    if (n === 0) return "không";
    if (n < 0) return "âm " + numberToVietnameseLocal(-n);
    
    const groups = [];
    let temp = n;
    while (temp > 0) {
      groups.push(temp % 1000);
      temp = Math.floor(temp / 1000);
    }
    if (groups.length === 0) groups.push(0);
    
    const readGroup = (g) => {
      if (g === 0) return "";
      const h = Math.floor(g / 100);
      const r = g % 100;
      let s = "";
      if (h > 0) s += DIGITS[h] + " trăm ";
      else if (groups.length > 1) s += "không trăm ";
      if (r === 0) return s.trim();
      if (r < 10) {
        s += (h > 0 && r === 5) ? "lẻ năm" : "lẻ " + DIGITS[r];
      } else if (r < 20) {
        s += "mười" + (r === 10 ? "" : (r === 15 ? " lăm" : " " + DIGITS[r % 10]));
      } else {
        const t = Math.floor(r / 10);
        const o = r % 10;
        s += ["", "", "hai mươi", "ba mươi", "bốn mươi", "năm mươi",
              "sáu mươi", "bảy mươi", "tám mươi", "chín mươi"][t];
        if (o === 1) s += " mốt";
        else if (o === 5) s += " lăm";
        else if (o > 0) s += " " + DIGITS[o];
      }
      return s.replace(/\s+/g, " ").trim();
    };
    
    let result = "";
    for (let i = groups.length - 1; i >= 0; i--) {
      const g = groups[i];
      if (g === 0) continue;
      result += readGroup(g);
      if (i > 0) result += " " + units[i] + " ";
    }
    return result.replace(/\s+/g, " ").trim();
  };
  
  // Match integers (including negative) - convert to Vietnamese words
  return text.replace(/\b-?\d+\b/g, (match) => {
    const num = parseInt(match);
    return numberToVietnameseLocal(num);
  });
}

/**
 * Smart normalizer: only run on Vietnamese text, only on final transcripts.
 */
export function smartNormalize(text, partial = false) {
  if (!text || partial) return text;
  if (containsVietnamese(text)) {
    return normalizeVietnameseText(text);
  }
  return text;
}

/**
 * Convert text for TTS: Vietnamese numbers to words for proper pronunciation.
 * Call this before sending text to TTS synthesis.
 */
export function prepareForTTS(text) {
  if (!text || typeof text !== "string") return text;
  if (containsVietnamese(text)) {
    return numbersToVietnameseWords(text);
  }
  return text;
}