"""Traditional Chinese localization helpers for public API payloads.""" from __future__ import annotations import re import unicodedata from typing import Any TEAM_NAMES = { 'Argentina': '阿根廷', 'Australia': '澳洲', 'Austria': '奧地利', 'Belgium': '比利時', 'Brazil': '巴西', 'Canada': '加拿大', 'Cape Verde': '維德角', 'Colombia': '哥倫比亞', 'Croatia': '克羅埃西亞', 'Curaçao': '古拉索', 'Czech Republic': '捷克', 'DR Congo': '剛果民主共和國', 'Ecuador': '厄瓜多', 'Egypt': '埃及', 'England': '英格蘭', 'France': '法國', 'Germany': '德國', 'Ghana': '迦納', 'Iran': '伊朗', 'Iraq': '伊拉克', 'Japan': '日本', 'Jordan': '約旦', 'Mexico': '墨西哥', 'Netherlands': '荷蘭', 'New Zealand': '紐西蘭', 'Norway': '挪威', 'Panama': '巴拿馬', 'Portugal': '葡萄牙', 'Qatar': '卡達', 'Saudi Arabia': '沙烏地阿拉伯', 'Senegal': '塞內加爾', 'South Africa': '南非', 'South Korea': '南韓', 'Spain': '西班牙', 'Sweden': '瑞典', 'Tunisia': '突尼西亞', 'Türkiye': '土耳其', 'Turkey': '土耳其', 'United States': '美國', 'USA': '美國', 'Uruguay': '烏拉圭', 'Uzbekistan': '烏茲別克', } COUNTRIES = { 'United States': '美國', 'USA': '美國', 'Canada': '加拿大', 'Mexico': '墨西哥', } CITIES = { 'New York': '紐約', 'New Jersey': '紐澤西', 'Los Angeles': '洛杉磯', 'Dallas': '達拉斯', 'Houston': '休士頓', 'Kansas City': '堪薩斯城', 'Miami': '邁阿密', 'Philadelphia': '費城', 'Seattle': '西雅圖', 'Atlanta': '亞特蘭大', 'Boston': '波士頓', 'San Francisco': '舊金山', 'Toronto': '多倫多', 'Vancouver': '溫哥華', 'Mexico City': '墨西哥城', 'Guadalajara': '瓜達拉哈拉', 'Monterrey': '蒙特雷', } VENUES = { 'MetLife Stadium': '大都會人壽體育場', 'SoFi Stadium': 'SoFi 體育場', 'AT&T Stadium': 'AT&T 體育場', 'NRG Stadium': 'NRG 體育場', 'Hard Rock Stadium': '硬石體育場', 'Lumen Field': '流明球場', 'Mercedes-Benz Stadium': '梅賽德斯-賓士體育場', 'Lincoln Financial Field': '林肯金融球場', 'Gillette Stadium': '吉列體育場', "Levi's Stadium": "李維斯體育場", 'BMO Field': 'BMO 球場', 'BC Place': '卑詩體育館', 'Estadio Azteca': '阿茲特克體育場', } MARKETS = { '1x2': '勝平負', 'h2h': '勝平負', 'h2h_3_way': '勝平負', 'asian_handicap': '亞洲讓球', 'spreads': '讓球盤', 'alternate_spreads': '讓球變體盤', 'ou': '大小球', 'totals': '大小球', 'alternate_totals': '大小球變體盤', 'btts': '雙方進球', 'draw_no_bet': '平手退回', 'team_total': '球隊大小球', 'team_totals': '球隊大小球', 'correct_score': '正確比分', } SELECTIONS = { 'home': '主隊', 'away': '客隊', 'draw': '平手', 'tie': '平手', 'over': '大分', 'under': '小分', 'yes': '是', 'no': '否', } STATUSES = { 'pre-match': '未開賽', 'scheduled': '未開賽', 'upcoming': '未開賽', 'in-play': '進行中', 'live': '進行中', 'finished': '已完賽', 'final': '已完賽', 'postponed': '延期', 'cancelled': '取消', 'canceled': '取消', } def _clean(value: Any) -> str: return str(value or '').strip() # External feeds and bookmakers use several spellings for the same national team. # Keep this list intentionally broad so the public API never leaks mixed English names. TEAM_NAMES.update( { "South Korea": "南韓", "Korea Republic": "南韓", "Republic of Korea": "南韓", "ROK": "南韓", "南韓": "南韓", "韓國": "南韓", "Czechia": "捷克", "Czech Republic": "捷克", "捷克": "捷克", "Bosnia and Herzegovina": "波士尼亞與赫塞哥維納", "Bosnia & Herzegovina": "波士尼亞與赫塞哥維納", "Bosnia-Herzegovina": "波士尼亞與赫塞哥維納", "Bosnia": "波士尼亞與赫塞哥維納", "Paraguay": "巴拉圭", "Algeria": "阿爾及利亞", "Morocco": "摩洛哥", "Switzerland": "瑞士", "Scotland": "蘇格蘭", "Wales": "威爾斯", "Poland": "波蘭", "Denmark": "丹麥", "Serbia": "塞爾維亞", "Chile": "智利", "Peru": "秘魯", "Venezuela": "委內瑞拉", "Bolivia": "玻利維亞", "Costa Rica": "哥斯大黎加", "Honduras": "宏都拉斯", "Jamaica": "牙買加", "Panama": "巴拿馬", "El Salvador": "薩爾瓦多", "Guatemala": "瓜地馬拉", "Haiti": "海地", "Trinidad and Tobago": "千里達及托巴哥", "Trinidad & Tobago": "千里達及托巴哥", "Curacao": "庫拉索", "Curaçao": "庫拉索", "Dominican Republic": "多明尼加共和國", "New Zealand": "紐西蘭", "Australia": "澳洲", "Saudi Arabia": "沙烏地阿拉伯", "Qatar": "卡達", "United Arab Emirates": "阿拉伯聯合大公國", "UAE": "阿拉伯聯合大公國", "Iraq": "伊拉克", "Jordan": "約旦", "Oman": "阿曼", "Uzbekistan": "烏茲別克", "Iran": "伊朗", "Japan": "日本", "China PR": "中國", "China": "中國", "North Korea": "北韓", "Cameroon": "喀麥隆", "Ghana": "迦納", "Ivory Coast": "象牙海岸", "Cote d'Ivoire": "象牙海岸", "Côte d’Ivoire": "象牙海岸", "Senegal": "塞內加爾", "Nigeria": "奈及利亞", "Tunisia": "突尼西亞", "Egypt": "埃及", "Mali": "馬利", "DR Congo": "剛果民主共和國", "Congo DR": "剛果民主共和國", "Cabo Verde": "維德角", "IR Iran": "伊朗", "Islamic Republic of Iran": "伊朗", "South Africa": "南非", } ) TEAM_NAME_ALIASES = { "usmnt": "United States", "usa": "United States", "u s a": "United States", "united states of america": "United States", "mex": "Mexico", "can": "Canada", "kor": "South Korea", "korea republic": "South Korea", "republic of korea": "South Korea", "czechia": "Czechia", "czech republic": "Czechia", "bosnia and herzegovina": "Bosnia and Herzegovina", "bosnia herzegovina": "Bosnia and Herzegovina", "bosnia": "Bosnia and Herzegovina", "paraguay": "Paraguay", } _GROUP_LABELS = {letter: letter for letter in "ABCDEFGHIJKL"} def _format_group_codes(raw_codes: str) -> str: codes = [code.strip().upper() for code in raw_codes.split('/') if code.strip()] return '、'.join(f'{_GROUP_LABELS.get(code, code)}組' for code in codes) def _localize_competition_placeholder(value: Any) -> str | None: text = _clean(value) if not text: return None match = re.fullmatch(r'Group\s+([A-L])\s+winners', text, flags=re.IGNORECASE) if match: return f'{match.group(1).upper()}組第1名' match = re.fullmatch(r'Group\s+([A-L])\s+runners-up', text, flags=re.IGNORECASE) if match: return f'{match.group(1).upper()}組第2名' match = re.fullmatch(r'Group\s+([A-L](?:/[A-L])+)\s+third\s+place', text, flags=re.IGNORECASE) if match: return f'{_format_group_codes(match.group(1))}第3名候選' match = re.fullmatch(r'Winner\s+Match\s+(\d+)', text, flags=re.IGNORECASE) if match: return f'第 {match.group(1)} 場勝方' match = re.fullmatch(r'Loser\s+Match\s+(\d+)', text, flags=re.IGNORECASE) if match: return f'第 {match.group(1)} 場敗方' return None def _normalize_lookup_key(value: Any) -> str: text = unicodedata.normalize("NFKD", _clean(value)) text = "".join(ch for ch in text if not unicodedata.combining(ch)) text = text.replace("&", " and ") text = re.sub(r"[^0-9A-Za-z\u4e00-\u9fff]+", " ", text) return re.sub(r"\s+", " ", text).strip().lower() def _lookup(value: Any, mapping: dict[str, str]) -> str: text = _clean(value) if not text: return '待確認' direct = mapping.get(text) or mapping.get(text.lower()) if direct: return direct normalized = _normalize_lookup_key(text) if mapping is TEAM_NAMES: alias = TEAM_NAME_ALIASES.get(normalized) if alias: alias_value = mapping.get(alias) or mapping.get(alias.lower()) if alias_value: return alias_value for key, translated in mapping.items(): if _normalize_lookup_key(key) == normalized: return translated return text def localize_team_name(value: Any) -> str: placeholder = _localize_competition_placeholder(value) if placeholder: return placeholder return _lookup(value, TEAM_NAMES) def localize_country(value: Any) -> str: return _lookup(value, COUNTRIES) def localize_city(value: Any) -> str: return _lookup(value, CITIES) def localize_venue_name(value: Any) -> str: return _lookup(value, VENUES) def localize_market_type(value: Any) -> str: return _lookup(value, MARKETS) def localize_selection(value: Any) -> str: text = _clean(value) if not text: return '待確認' lowered = text.lower() return SELECTIONS.get(lowered, TEAM_NAMES.get(text, text)) def localize_status(value: Any) -> str: text = _clean(value) if not text: return '待確認' return STATUSES.get(text.lower().replace('_', '-'), text)