File size: 5,035 Bytes
65ac460
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
}