| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| 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(); |
| } |
|
|
| |
| |
| |
| |
| export function normalizeVietnameseText(text) { |
| if (!text || typeof text !== "string") return text; |
| return text.replace(/\s+/g, " ").trim(); |
| } |
|
|
| |
| |
| |
| export function containsVietnamese(text) { |
| return /[àáạảãâầấậẩẫăằắặẳẵèéẹẻẽêềếệểễìíịỉĩòóọỏõôồốộổỗơờớợởỡùúụủũưừứựửữỳýỵỷỹđ]/i.test(text); |
| } |
|
|
| |
| |
| |
| |
| 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(); |
| }; |
| |
| |
| return text.replace(/\b-?\d+\b/g, (match) => { |
| const num = parseInt(match); |
| return numberToVietnameseLocal(num); |
| }); |
| } |
|
|
| |
| |
| |
| export function smartNormalize(text, partial = false) { |
| if (!text || partial) return text; |
| if (containsVietnamese(text)) { |
| return normalizeVietnameseText(text); |
| } |
| return text; |
| } |
|
|
| |
| |
| |
| |
| export function prepareForTTS(text) { |
| if (!text || typeof text !== "string") return text; |
| if (containsVietnamese(text)) { |
| return numbersToVietnameseWords(text); |
| } |
| return text; |
| } |