gemma-multi-avatar / src /viNumberFix.js
bep40's picture
Upload src/viNumberFix.js
ca69da8 verified
Raw
History Blame
5.04 kB
/**
* 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;
}