import json import re from pathlib import Path _LATIN_RUN_RE = re.compile(r"[A-Za-z]+") # Фонетические правила en->ru. Применяются слева направо, длиннейшее # совпадение выигрывает (триграфы раньше диграфов, диграфы раньше букв). _RULES: dict[str, str] = { "tion": "шн", "sure": "шур", "ture": "тур", "ough": "оф", "tch": "ч", "sch": "ш", "igh": "ай", "ng": "нг", "nk": "нк", "qu": "кв", "th": "т", "sh": "ш", "ch": "ч", "ck": "к", "ph": "ф", "wh": "у", "wr": "р", "kn": "н", "ee": "и", "ea": "и", "oo": "у", "ou": "ау", "ow": "ау", "ai": "ай", "ay": "эй", "oi": "ой", "oy": "ой", "au": "о", "aw": "о", "ir": "ер", "ur": "ер", "er": "ер", "ar": "ар", "or": "ор", "a": "а", "b": "б", "c": "к", "d": "д", "e": "е", "f": "ф", "g": "г", "h": "х", "i": "и", "j": "дж", "k": "к", "l": "л", "m": "м", "n": "н", "o": "о", "p": "п", "q": "к", "r": "р", "s": "с", "t": "т", "u": "у", "v": "в", "w": "у", "x": "кс", "y": "и", "z": "з", } _RULE_KEYS = sorted(_RULES, key=len, reverse=True) _dict_path = Path("translit_dict.json") _cache: tuple[dict[str, str], dict[str, str]] | None = None def set_dict_path(path: str | Path) -> None: global _dict_path, _cache _dict_path = Path(path) _cache = None def _load_dict() -> tuple[dict[str, str], dict[str, str]]: global _cache if _cache is None: data = json.loads(_dict_path.read_text(encoding="utf-8")) exact = {k.lower(): v for k, v in data.get("exact", {}).items()} abbrev = {k.upper(): v for k, v in data.get("abbreviations", {}).items()} _cache = (exact, abbrev) return _cache def _apply_rules(word: str) -> str: out: list[str] = [] i = 0 n = len(word) while i < n: for key in _RULE_KEYS: if word.startswith(key, i): out.append(_RULES[key]) i += len(key) break else: out.append(word[i]) i += 1 result = "".join(out) if word.endswith("e") and len(result) > 2 and result.endswith("е"): result = result[:-1] return result def _match_case(token: str, lower_out: str) -> str: if token.isupper(): return lower_out.upper() if token[:1].isupper(): return lower_out.capitalize() return lower_out def transliterate_token(token: str) -> str: exact, abbrev = _load_dict() if token.isupper() and token in abbrev: return abbrev[token] key = token.lower() if key in exact: return _match_case(token, exact[key]) return _match_case(token, _apply_rules(key)) def transliterate_latin(text: str) -> str: return _LATIN_RUN_RE.sub(lambda m: transliterate_token(m.group()), text)