File size: 11,747 Bytes
2e99c77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import re
from underthesea import text_normalize

# Dictionary to map numbers to Vietnamese words
number_to_words = {
    0: 'không',
    1: 'một',
    2: 'hai',
    3: 'ba',
    4: 'bốn',
    5: 'năm',
    6: 'sáu',
    7: 'bảy',
    8: 'tám',
    9: 'chín',
    10: 'mười',
    100: 'trăm',
    1000: 'nghìn',
    1000000: 'triệu',
    1000000000: 'tỷ'
}

# Dictionary to map Roman numerals to integers
roman_to_int = {
    'I': 1,
    'V': 5,
    'X': 10,
    'L': 50,
    'C': 100,
    'D': 500,
    'M': 1000
}

# Function to convert Roman numerals to integers
def roman_to_integer(roman):
    total = 0
    prev_value = 0
    for char in reversed(roman):
        value = roman_to_int.get(char, 0)
        if value < prev_value:
            total -= value
        else:
            total += value
        prev_value = value
    return total
    
currency_symbols ={
    '~': '~ ',
    '%': 'phần trăm',
    '$': 'đô la',
    '₫': 'đồng',
    'đ': 'đồng',
    '€': 'ơ rô',
    '£': 'bảng',
    '¥': 'yên',
    '₹': 'ru pi',
    '₽': 'rúp',
    '₺': 'li ra',
    '₩': 'uôn',
}

def currency_symbol_to_word(currency_sign):
    if currency_sign in currency_symbols:
        return currency_symbols[currency_sign]
    return currency_sign
    
def detect_number_format(number_str):
    # Check if the number contains a comma and a dot
    if ',' in number_str and '.' in number_str:
        # If the last comma is after the last dot, it's Vietnamese
        if number_str.rfind(',') > number_str.rfind('.'):
            # Validate Vietnamese format
            if re.match(r'^\d{1,3}(?:\.\d{3})*(?:,\d+)?$', number_str):
                return "Vietnamese"
            else:
                return "Invalid"
        # Otherwise, it's US
        else:
            # Validate US format
            if re.match(r'^\d{1,3}(?:,\d{3})*(?:\.\d+)?$', number_str):
                return "US"
            else:
                return "Invalid"
    # If only commas are present
    elif ',' in number_str:
        if re.match(r'^\d{1,3}(?:,\d{3})*(?:\.\d+)?$', number_str):
            return "US"
        elif re.match(r'^(\d+,\d+)?$', number_str):
            return "Vietnamese"
        else:
            return "Invalid"
    # If only dots are present
    elif '.' in number_str:
        if re.match(r'^\d{1,3}(?:\.\d{3})*(?:,\d+)?$', number_str):
            return "Vietnamese"
        elif re.match(r'^(\d+\.\d+)?$', number_str):
            return "US"
        else:
            return "Invalid"
    # If no separators are present, assume Vietnamese (default)
    else:
        return "Vietnamese"

# Function to convert numbers to Vietnamese words
def number_to_vietnamese_words(number_str):
    number_str = str(number_str)
    if detect_number_format(number_str) == 'Invalid':
        return number_str
        
    if detect_number_format(number_str) == 'US': # convert US number to Vietnamese one: 1,234.5 to 1234,5
        number = re.sub(r'\.', ',', re.sub(r',', '', number_str))
    else: # remove any dot inside number
        number = re.sub(r'\.', '', number_str)

    if isinstance(number, str) and ',' in number:
        # Handle decimal numbers (e.g., "120,57")
        integer_part, decimal_part = number.split(',')
        integer_words = _convert_integer_part(int(integer_part))
        decimal_words = _convert_decimal_part(decimal_part)
        return f"{integer_words} phẩy {decimal_words}"
    else:
        # Handle integer numbers
        return _convert_integer_part(int(number))

# Helper function to convert the integer part of a number
def _convert_integer_part(number):
    if number == 0:
        return number_to_words[0]

    words = []
    
    # Handle billions
    if number >= 1000000000:
        billion = number // 1000000000
        words.append(_convert_integer_part(billion))
        words.append(number_to_words[1000000000])
        number %= 1000000000
    
    # Handle millions
    if number >= 1000000:
        million = number // 1000000
        words.append(_convert_integer_part(million))
        words.append(number_to_words[1000000])
        number %= 1000000
    
    # Handle thousands
    if number >= 1000:
        thousand = number // 1000
        words.append(_convert_integer_part(thousand))
        words.append(number_to_words[1000])
        number %= 1000
        if number < 100 and number > 0:
            words.append('không trăm')
        if number < 10 and number > 0:
            words.append('không')
    
    # Handle hundreds
    if number >= 100:
        hundred = number // 100
        words.append(number_to_words[hundred])
        words.append(number_to_words[100])
        number %= 100
        if number > 0 and number < 10:
            words.append('lẻ')  # Add "lẻ" for numbers like 106 (một trăm lẻ sáu)
    
    # Handle tens and units
    if number >= 20:
        ten = number // 10
        words.append(number_to_words[ten])
        words.append('mươi')
        number %= 10
    elif number >= 10:
        words.append(number_to_words[10])
        number %= 10
    
    # Handle units (1-9)
    if number > 0:
        if number == 5 and len(words) > 1 and not words[-1] in['lẻ', 'không']: w = 'lăm'
        elif number == 1 and len(words) > 1 and not words[-1] in ['lẻ', 'mười', 'không']: w = 'mốt'
        else:  w = number_to_words[number]
        words.append(w)
    
    return ' '.join(words)


# Helper function to convert the decimal part of a number
def _convert_decimal_part(decimal_part):
    words = []
    for digit in decimal_part:
        words.append(number_to_words[int(digit)])
    return ' '.join(words)

# abbreviation replacement    
abbreviation_map = {
    "AI": "Ây Ai",
    "ASEAN": "A Xê An",
    "ATGT": "An toàn giao thông",
    "BCA": "Bộ Công an",
    "BCH": "Ban chấp hành",
    "BCHTW": "Ban Chấp hành Trung ương",
    "BCT": "Bộ Chính trị",
    "BGD": "Bộ Giáo dục",
    "BKH": "Bộ Khoa học và Công nghệ",
    "BNN": "Bộ Nông nghiệp",
    "BQP": "Bộ Quốc phòng",
    "BTC": "Ban tổ chức",
    "BTL": "Bộ Tư lệnh",
    "BYT": "Bộ Y tế",
    "CA" : "công an",
    "CAND" : "Công an nhân dân",
    "CNCS": "chủ nghĩa cộng sản",
    "CNTB": "chủ nghĩa tư bản",
    "CNXH": "chủ nghĩa xã hội",
    "CNY": "nhân dân tệ",
    "CSGT": "Cảnh sát giao thông",
    "CTN": "Chủ tịch nước",
    "ĐBQH": "Đại biểu Quốc hội",
    "ĐBSCL": "Đồng bằng sông Cửu Long",
    "ĐCS": "Đảng cộng sản",
    "ĐH": "Đại học",
    "ĐHBK": "Đại học Bách khoa",
    "ĐHKHTN": "Đại học Khoa học tự nhiên",
    "ĐHQG": "Đại học Quốc gia",
    "ĐSQ": "Đại sứ quán",
    "EU": "Ơ u",
    "GD": "Giáo dục",
    "HCM": "Hồ Chí Minh",
    "HĐBA": "Hội đồng bảo an",
    "HĐND": "Hội đồng nhân dân",
    "HĐQT": "Hội đồng quản trị",
    "HN": "Hà Nội",
    "HV": "Học viện",
    "KHXH&NV": "Khoa học Xã hội và Nhân văn",
    "KT": "Kinh tế",
    "KTQS": "Kỹ thuật Quân sự",
    "LĐ": "lao động",
    "KHKT": "khoa học kỹ thuật",
    "km": "ki lô mét",
    "LHQ": "Liên Hiệp Quốc",
    "NATO": "Na tô",
    "ND": "nhân dân",
    "NHNN": "ngân hàng nhà nước",
    "NXB": "Nhà xuất bản",
    "PCCC": "Phòng cháy chữa cháy",
    "PTTH": "Phổ thông trung học",
    "PTCS": "Phổ thông cơ sở",
    "QĐND" : "Quân đội nhân dân",
    "QĐNDVN" : "Quân đội nhân dân Việt Nam",
    "QG": "Quốc gia",
    "QK": "Quân khu",
    "sau CN": "sau công nguyên",
    "SG": "Sài Gòn",
    "TAND": "Tòa án nhân dân",
    "TBCN": "tư bản chủ nghĩa",
    "TBT": "Tổng bí thư",
    "TCN": "trước công nguyên",
    "TCT": "Tổng công ty",
    "THCS": "Trung học cơ sở",
    "THPT": "Trung học phổ thông",
    "TNHH": "Trách nhiệm hữu hạn",
    "TNHH MTV": "Trách nhiệm hữu hạn một thành viên",
    "TP": "thành phố",
    "TP.": "thành phố",
    "TPHCM": "Thành phố Hồ Chí Minh",
    "TT": "Thủ tướng",
    "TTCK": "Thị trường chứng khoán",
    "TTTC": "Thị trường tài chính",
    "TTCP": "Thủ tướng chính phủ",
    "TTNT": "Trí tuệ nhân tạo",
    "TTXVN": "Thông tấn xã Việt Nam",
    "TƯ": "Trung ương",
    "TW": "Trung ương",
    "UB": "Ủy ban",
    "UBND": "Ủy ban nhân dân",
    "VH": "Văn hóa",
    "VKSND": "Viện kiểm sát nhân dân",
    "VN": "Việt Nam",
    "VND": "Việt Nam đồng",
    "XH": "Xã hội",
    "XHCN": "xã hội chủ nghĩa",
    "%": "phần trăm",
    "@": "a còng",
    "&": "và",
}

abbreviation_pattern = re.compile(r'\b(' + '|'.join(re.escape(key) for key in abbreviation_map.keys()) + r')\b')
def replace_abbreviations(text):
    def replacement(match):
        return abbreviation_map[match.group(0)]
    return abbreviation_pattern.sub(replacement, text)


def convert_abbreviations(text):
    """Converts abbreviations like M.A.S.H. to MASH"""
    return re.sub(r"([A-Z]\.){2,}", lambda match: "".join(c for c in match.group(0) if c.isalpha()), text)


# Function to normalize Vietnamese text
def normalize_vietnamese_text(text):
    text = text_normalize(text)
    
    def replace_slash_with_word(text):
        def replacement(match):
            word = match.group(1)
            if word in ['ngày', 'giờ', 'tháng', 'quí', 'quý', 'năm']:
                return f" mỗi {word}"
            else:
                return f" trên {word}"
        return re.sub(r'/(\w+)', replacement, text)

    # find and replace "/word" with "per word"
    text = replace_slash_with_word(text)

    # Convert standalone currency amounts (e.g., $200, ₫200, €50, £75, ¥1000)
    def replace_currency(match):
        currency_sign = match.group(1)
        amount = match.group(2)
        return f"{number_to_vietnamese_words(amount)} {currency_symbol_to_word(currency_sign)}"
    text = re.sub(r'([$₫đ€£¥₹₽₩₺])([\d.,]+)', replace_currency, text)
    
    # (reverse case) convert standalone currency amounts (e.g., 200$, 200đ, 50€, 75£, 1000¥)
    def replace_currency_suffix(match):
        amount = match.group(1)
        currency_sign = match.group(2)
        return f"{number_to_vietnamese_words(amount)} {currency_symbol_to_word(currency_sign)}"
    text = re.sub(r'([\d.,]+)([$₫đ€£¥₹₽₩₺%])', replace_currency_suffix, text)
    
    # in case symbol [¥] is used for Chinese currency and followed by CNY
    text = text.replace('yên CNY', 'nhân dân tệ')
    
    # Replace abbreviations
    text = convert_abbreviations(text)
    text = replace_abbreviations(text)
    
    # Convert Roman numerals to integers
    def replace_roman(match):
        roman_numeral = match.group()
        return str(roman_to_integer(roman_numeral))
    # Replace Roman numerals with integers
    text = re.sub(r'\b[IVXLCDM]+\b', replace_roman, text)

    # Convert standalone numbers to words
    text = re.sub(r'\b[\d.,]+\b', lambda match: number_to_vietnamese_words(match.group()), text)
    
    # Fix common grammar errors
    text = re.sub(r'\s+', ' ', text)  # Remove extra spaces
    text = re.sub(r'\s([,\.])', r'\1', text)  # Remove space before punctuation
    text = re.sub(r'([,\.])(\S)', r'\1 \2', text)  # Add space after punctuation
    
    text = ( text.replace("..", ".")
                .replace("!.", "!")
                .replace("?.", "?")
                .replace(" .", ".")
                .replace(" ,", ",")
                .replace(" (", ", ")
                .replace(") ", ", ")
    )

    return text.strip()