""" teencode_normalizer.py ────────────────────── Vietnamese teencode / internet-slang → standard-form normalizer. Usage: from teencode_normalizer import normalize_teencode clean = normalize_teencode("ko bik j luôn á") # → "không biết gì luôn á" The mapping dictionary is hand-curated for the Vietnamese toxic-comment classification task (ViHOS / UIT-VSMEC style data). It intentionally preserves vulgar terms in their full form so downstream classifiers can learn to detect them. All replacements are **case-insensitive** and respect word boundaries so that partial matches inside longer words are not corrupted. """ import re from typing import Dict # ──────────────────────────────────────────────────────────────────── # Teencode → Standard Vietnamese mapping # ──────────────────────────────────────────────────────────────────── TEENCODE_MAP: Dict[str, str] = { # ── Common abbreviations ────────────────────────────────────── "ko": "không", "k": "không", "kg": "không", "khg": "không", "kh": "không", "hk": "không", "hem": "không", "hông": "không", "kô": "không", "0": "không", # single-digit zero used as "không" "dc": "được", "dk": "được", "đc": "được", "đk": "được", "duoc": "được", "ns": "nói", "nch": "nói chuyện", "nc": "nói chuyện", "bt": "bình thường", "bth": "bình thường", "bn": "bao nhiêu", "bnhieu": "bao nhiêu", "bh": "bao giờ", "bjh": "bao giờ", "lm": "làm", "lam": "làm", "mk": "mình", "mik": "mình", "mn": "mọi người", "mng": "mọi người", "ng": "người", "ngta": "người ta", "nta": "người ta", "nt": "nhắn tin", "tl": "trả lời", "rep": "trả lời", "ib": "inbox", "fb": "facebook", "acc": "tài khoản", "ad": "admin", "amin": "admin", "adm": "admin", "a": "anh", "e": "em", "aj": "ai", "aq": "ai", "j": "gì", "ji": "gì", "z": "vậy", "v": "vậy", "vl": "vãi", "r": "rồi", "oy": "rồi", "rui": "rồi", "roi": "rồi", "m": "mày", "t": "tao", "tui": "tôi", "cx": "cũng", "cg": "cũng", "cug": "cũng", "cux": "cũng", "vs": "với", "voi": "với", "nma": "nhưng mà", "nhma": "nhưng mà", "trc": "trước", "trog": "trong", "trg": "trong", "ngoai": "ngoài", "ngòai": "ngoài", "dg": "đường", "đg": "đường", "hiu": "hiểu", "bik": "biết", "biet": "biết", "bit": "biết", "wa": "quá", "qá": "quá", "qa": "quá", "lun": "luôn", "luon": "luôn", "nek": "nè", "nè": "nè", "nha": "nhé", "nhaz": "nhé", "nhen": "nhé", "hen": "nhé", "thik": "thích", "thix": "thích", "pn": "phụ nữ", "ck": "chồng", "vk": "vợ", "gd": "gia đình", "gđ": "gia đình", "xh": "xã hội", "dt": "điện thoại", "đt": "điện thoại", "sdt": "số điện thoại", "zui": "vui", "zậy": "vậy", "zì": "gì", "dzậy": "vậy", "dz": "vậy", "dậy": "vậy", "vậy": "vậy", "mí": "với", "iu": "yêu", "yeu": "yêu", "cmt": "bình luận", "comt": "bình luận", "share": "chia sẻ", "fl": "theo dõi", "like": "thích", "sub": "đăng ký", "clip": "video", "tks": "cảm ơn", "thanks": "cảm ơn", "tq": "cảm ơn", "ty": "cảm ơn", "sr": "xin lỗi", "sorry": "xin lỗi", "plz": "xin hãy", "pls": "xin hãy", "ok": "được", "oke": "được", "okie": "được", "okee": "được", "ngon": "ngon", "xin": "xin", "ot": "tăng ca", "cty": "công ty", "cong ty": "công ty", "nyc": "người yêu cũ", "ny": "người yêu", "hs": "học sinh", "sv": "sinh viên", "gv": "giáo viên", "hsg": "học sinh giỏi", "ctv": "cộng tác viên", "hcm": "hồ chí minh", "hn": "hà nội", "sg": "sài gòn", "vn": "việt nam", "nck": "nghiên cứu khoa học", "đh": "đại học", "dh": "đại học", "thpt": "trung học phổ thông", "thcs": "trung học cơ sở", "ths": "thạc sĩ", "ts": "tiến sĩ", "gs": "giáo sư", "pgs": "phó giáo sư", # ── Vulgar / toxic teencode ─────────────────────────────────── # (kept in full form intentionally for hate-speech detection) "vcl": "vãi cả lồn", "vkl": "vãi cả lồn", "vcc": "vãi cả chưởng", "vl": "vãi lồn", "vcđ": "vãi cả đái", "đm": "đụ mẹ", "dm": "đụ mẹ", "dmm": "đụ mẹ mày", "đmm": "đụ mẹ mày", "dkm": "đụ con mẹ", "đkm": "đụ con mẹ", "dcm": "đụ con mẹ", "đcm": "đụ con mẹ", "clm": "con lồn mày", "cl": "cái lồn", "cmm": "con mẹ mày", "cc": "cặc", "ccc": "cặc", "ccm": "cái cặc mày", "bkl": "bố mày cái lồn", "đéo": "đéo", "deo": "đéo", "đù": "đụ", "du": "đụ", "wtf": "cái quái gì", "stfu": "câm mồm đi", "lol": "haha", "lmao": "haha", "bruh": "bạn ơi", "omg": "trời ơi", "haiz": "thở dài", "huhu": "khóc", "hihi": "hehe", "kk": "haha", "kkk": "haha", "haha": "haha", "hehe": "hehe", "tl": "trả lời", "tks": "cảm ơn", "ngu": "ngu", "óc chó": "ngu", "vkđ": "vãi cả đái", # ── Obfuscated spellings ────────────────────────────────────── "rag": "rằng", "trog": "trong", "nguoi": "người", "ngui": "người", "nuoc": "nước", "nc": "nước", "nhug": "nhưng", "nug": "nhưng", "vâg": "vâng", "thg": "thằng", "thag": "thằng", "thăg": "thằng", "cog": "công", "nhà nc": "nhà nước", "ks": "khách sạn", "bv": "bệnh viện", "sg": "sài gòn", "cp": "chính phủ", "td": "thủ đô", # ── Elongated / repeated chars (handled by regex below) ─────── # ── Gen-Z / newer slang ─────────────────────────────────────── "crush": "người thương", "flex": "khoe", "slay": "quá đỉnh", "real": "thật", "cap": "nói dối", "nocap": "thật luôn", "no cap": "thật luôn", "ib": "nhắn riêng", "toxic": "độc hại", "simp": "theo đuổi mù quáng", "gato": "ghen ăn tức ở", "hóng": "tò mò", "ship": "ghép đôi", "out trình": "vượt trội", "bóc phốt": "tố cáo", "trẩu": "ngu", "đu trend": "theo xu hướng", "chill": "thư giãn", "vibe": "cảm giác", "deep": "sâu sắc", "ảo tung chảo": "quá ảo", "chằm zn": "trầm cảm", "xàm": "nhảm nhí", "xàm lol": "nhảm nhí", "gấu": "người yêu", "cẩu lương": "tình cảm ngọt ngào", "sến": "sến súa", "xịn xò": "tốt đẹp", "gét gô": "let's go", "ét ô ét": "kêu cứu", "u là trời": "trời ơi", } # ──────────────────────────────────────────────────────────────────── # Build the compiled regex once at import time. # Sort by length (longest first) so that multi-word keys like # "nhà nc" match before the single-word "nc". # ──────────────────────────────────────────────────────────────────── _SORTED_KEYS = sorted(TEENCODE_MAP.keys(), key=len, reverse=True) # Build pattern: each key is escaped and wrapped in word-boundaries. # Vietnamese word boundaries need a bit of care — we use lookbehind / # lookahead for whitespace-or-string-edge so that diacritics are not # treated as non-word characters by \b. _PATTERN = re.compile( r"(? str: """Return the standard form for the matched teencode token.""" token = match.group(0).lower() return TEENCODE_MAP.get(token, token) # ──────────────────────────────────────────────────────────────────── # Additional regex rules applied *after* dictionary replacement # ──────────────────────────────────────────────────────────────────── # 1. Collapse runs of 3+ identical characters → 2 (e.g. "đẹpppp" → "đẹpp") _REPEAT_CHAR = re.compile(r"(.)\1{2,}") # 2. Normalise whitespace _MULTI_SPACE = re.compile(r"\s{2,}") def normalize_teencode(text: str) -> str: """ Normalise Vietnamese teencode / internet slang in *text*. Steps ----- 1. Dictionary-based word replacement (case-insensitive, word-boundary aware). 2. Collapse repeated characters (≥ 3 identical → 2). 3. Strip leading / trailing whitespace and collapse internal runs. Parameters ---------- text : str Raw Vietnamese comment text. Returns ------- str Text with teencode replaced by standard Vietnamese. """ if not isinstance(text, str) or not text: return text # Step 1 – dictionary replacement text = _PATTERN.sub(_replace, text) # Step 2 – collapse repeated characters text = _REPEAT_CHAR.sub(r"\1\1", text) # Step 3 – whitespace tidy-up text = _MULTI_SPACE.sub(" ", text).strip() return text # ──────────────────────────────────────────────────────────────────── # Quick smoke test when run as a script # ──────────────────────────────────────────────────────────────────── if __name__ == "__main__": examples = [ "ko bik j luôn á", "mng ơi ad ib e vs nha", "thg này ngu vcl đm", "sao m ko rep t z", "chill quá đi hehe crush iu wa", "Bọn này toàn xàm lol vcl dmm", "ét ô ét ảo tung chảo luôn", "đẹppppp quáaaaaa", ] for ex in examples: print(f" IN: {ex}") print(f" OUT: {normalize_teencode(ex)}") print()