restore-diacritics.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #!/usr/bin/env python3
  2. """
  3. Restores stripped diacritics in the locale files.
  4. Only words whose unaccented form is not itself a valid word in that language
  5. are touched. Pairs like French supprime/supprimé, Spanish esta/está or Polish
  6. faktura/fakturą are real distinct words and are left alone: a script cannot
  7. tell which one a given string meant, and guessing would corrupt correct text.
  8. Every replacement below was taken from spellings the same locale already uses
  9. elsewhere, so nothing is invented.
  10. """
  11. import json
  12. import pathlib
  13. import re
  14. import sys
  15. ROOT = pathlib.Path("/home/sinamics/torqvoice/messages")
  16. FIXES = {
  17. "fr": {
  18. "parametres": "paramètres", "parametre": "paramètre", "echec": "échec",
  19. "donnees": "données", "defaut": "défaut", "numero": "numéro",
  20. "numeros": "numéros", "enregistres": "enregistrés", "vehicules": "véhicules",
  21. "vehicule": "véhicule", "pieces": "pièces", "piece": "pièce",
  22. "acces": "accès", "modele": "modèle", "modeles": "modèles",
  23. "etre": "être", "definitivement": "définitivement", "equipe": "équipe",
  24. "telephone": "téléphone", "details": "détails", "telecharger": "télécharger",
  25. "telechargement": "téléchargement", "apercu": "aperçu", "creer": "créer",
  26. "creez": "créez", "creation": "création", "annulee": "annulée",
  27. "desactiver": "désactiver", "verifier": "vérifier", "verifiez": "vérifiez",
  28. "verification": "vérification", "terminee": "terminée",
  29. "fonctionnalites": "fonctionnalités", "tete": "tête",
  30. "selectionnez": "sélectionnez", "selectionner": "sélectionner",
  31. "deselectionner": "désélectionner", "succes": "succès", "region": "région",
  32. "echouee": "échouée", "envoyes": "envoyés", "envoyee": "envoyée",
  33. "pret": "prêt", "prete": "prête", "echeance": "échéance", "annee": "année",
  34. "irreversible": "irréversible", "partagees": "partagées",
  35. "associees": "associées", "definir": "définir", "definitions": "définitions",
  36. "generees": "générées", "generer": "générer", "generation": "génération",
  37. "deja": "déjà", "debut": "début", "creee": "créée",
  38. "caracteres": "caractères", "gerer": "gérer", "proprietaire": "propriétaire",
  39. "systeme": "système", "secrete": "secrète", "securite": "sécurité",
  40. "reessayer": "réessayer", "desactivee": "désactivée",
  41. "visibilite": "visibilité", "kilometrage": "kilométrage",
  42. "controle": "contrôle", "enregistree": "enregistrée", "derniere": "dernière",
  43. "reparations": "réparations", "liee": "liée", "configuree": "configurée",
  44. "reception": "réception", "recu": "reçu", "categories": "catégories",
  45. "recuperer": "récupérer", "egalement": "également", "hote": "hôte",
  46. "expediteur": "expéditeur", "reinitialiser": "réinitialiser",
  47. "eligibles": "éligibles", "reussie": "réussie",
  48. "supplementaire": "supplémentaire", "salgsvilkar": "salgsvilkår",
  49. },
  50. "es": {
  51. "configuracion": "configuración", "contrasena": "contraseña",
  52. "contrasenas": "contraseñas", "vehiculo": "vehículo",
  53. "vehiculos": "vehículos", "numero": "número", "numeros": "números",
  54. "codigo": "código", "organizacion": "organización",
  55. "electronico": "electrónico", "invitacion": "invitación",
  56. "aplicacion": "aplicación", "sesion": "sesión", "ordenes": "órdenes",
  57. "pagina": "página", "titulo": "título", "accion": "acción",
  58. "telefono": "teléfono", "dias": "días",
  59. "automaticamente": "automáticamente", "cotizacion": "cotización",
  60. "importacion": "importación", "tecnico": "técnico",
  61. "inspeccion": "inspección", "conexion": "conexión",
  62. "mensajeria": "mensajería", "ningun": "ningún", "eliminara": "eliminará",
  63. "proximo": "próximo", "verificacion": "verificación",
  64. "direccion": "dirección", "region": "región", "diseno": "diseño",
  65. "informacion": "información", "garantia": "garantía",
  66. "imagenes": "imágenes", "gestion": "gestión", "valido": "válido",
  67. "linea": "línea", "actualizacion": "actualización",
  68. "encontro": "encontró", "suscripcion": "suscripción",
  69. "digitos": "dígitos", "facturacion": "facturación", "mecanico": "mecánico",
  70. "tamano": "tamaño", "descripcion": "descripción", "terminos": "términos",
  71. "miercoles": "miércoles", "sabado": "sábado", "busqueda": "búsqueda",
  72. "reparacion": "reparación", "aqui": "aquí", "traves": "través",
  73. "conservara": "conservará", "atras": "atrás",
  74. "continuacion": "continuación", "categorias": "categorías",
  75. "establecera": "establecerá", "ultima": "última", "tambien": "también",
  76. "implicito": "implícito", "produccion": "producción",
  77. "intentelo": "inténtelo", "envio": "envío", "perderan": "perderán",
  78. "vacio": "vacío", "visualizacion": "visualización",
  79. "minusculas": "minúsculas", "maximo": "máximo",
  80. "transmision": "transmisión", "salgsvilkar": "salgsvilkår",
  81. },
  82. "pt-BR": {
  83. "configuracoes": "configurações", "configuracao": "configuração",
  84. "servico": "serviço", "servicos": "serviços", "funcao": "função",
  85. "orcamentos": "orçamentos", "orcamento": "orçamento", "numero": "número",
  86. "numeros": "números", "organizacao": "organização",
  87. "organizacoes": "organizações", "padrao": "padrão", "veiculos": "veículos",
  88. "veiculo": "veículo", "licenca": "licença", "codigo": "código",
  89. "codigos": "códigos", "autenticacao": "autenticação",
  90. "condicoes": "condições", "pecas": "peças", "peca": "peça",
  91. "conteudo": "conteúdo", "historico": "histórico", "sera": "será",
  92. "inventario": "inventário", "bancaria": "bancária",
  93. "manutencao": "manutenção", "acao": "ação", "usuario": "usuário",
  94. "usuarios": "usuários", "visualizacao": "visualização",
  95. "importacao": "importação", "conexao": "conexão", "conexoes": "conexões",
  96. "voce": "você", "serao": "serão", "concluida": "concluída",
  97. "concluido": "concluído", "aparencia": "aparência",
  98. "cabecalho": "cabeçalho", "tecnico": "técnico", "inspecao": "inspeção",
  99. "inspecoes": "inspeções", "excluido": "excluído", "excluida": "excluída",
  100. "excluira": "excluirá", "proximo": "próximo", "proxima": "próxima",
  101. "proximos": "próximos", "verificacao": "verificação",
  102. "endereco": "endereço", "regiao": "região", "informacoes": "informações",
  103. "liquido": "líquido", "alteracoes": "alterações", "comecar": "começar",
  104. "atualizacao": "atualização", "transferencia": "transferência",
  105. "inicio": "início", "pagina": "página", "proprietario": "proprietário",
  106. "proprio": "próprio", "estao": "estão", "horario": "horário",
  107. "horarios": "horários", "permissao": "permissão",
  108. "combustivel": "combustível", "seguranca": "segurança",
  109. "exibicao": "exibição", "area": "área", "preco": "preço",
  110. "precos": "preços", "incluido": "incluído", "relatorio": "relatório",
  111. "terca": "terça", "sabado": "sábado", "cartao": "cartão",
  112. "possivel": "possível", "definira": "definirá", "tambem": "também",
  113. "implicito": "implícito", "producao": "produção", "forcar": "forçar",
  114. "dominio": "domínio", "digitos": "dígitos", "invalido": "inválido",
  115. "solicitacoes": "solicitações", "joao": "joão", "secoes": "seções",
  116. "definicoes": "definições", "perderao": "perderão", "rodape": "rodapé",
  117. "preferencia": "preferência", "elegiveis": "elegíveis",
  118. "expiracao": "expiração", "minusculas": "minúsculas",
  119. "atribuida": "atribuída", "ardosia": "ardósia", "ultima": "última",
  120. "destinatario": "destinatário", "salgsvilkar": "salgsvilkår",
  121. },
  122. "pl": {
  123. "usun": "usuń", "usunac": "usunąć", "wiadomosci": "wiadomości",
  124. "rekordow": "rekordów", "wprowadz": "wprowadź",
  125. "wprowadzic": "wprowadzić", "utworz": "utwórz", "utworzyc": "utworzyć",
  126. "ustawien": "ustawień", "dostep": "dostęp", "dostepu": "dostępu",
  127. "domyslna": "domyślna", "domyslne": "domyślne", "domyslny": "domyślny",
  128. "domyslnej": "domyślnej", "istniejacych": "istniejących",
  129. "istniejace": "istniejące", "czesci": "części", "czesc": "część",
  130. "czesciowa": "częściowa", "zapisac": "zapisać",
  131. "zaktualizowac": "zaktualizować", "zakonczony": "zakończony",
  132. "zlecen": "zleceń", "klientow": "klientów", "pokaz": "pokaż",
  133. "przeslij": "prześlij", "moze": "może", "mozesz": "możesz",
  134. "mozna": "można", "moga": "mogą", "zawartosc": "zawartość",
  135. "podglad": "podgląd", "miedzy": "między", "cofnac": "cofnąć",
  136. "wyslij": "wyślij", "uzyj": "użyj", "uzyc": "użyć", "uzyto": "użyto",
  137. "pomyslnie": "pomyślnie", "uzytkownik": "użytkownik",
  138. "uzytkownika": "użytkownika", "uzytkownikami": "użytkownikami",
  139. "twoj": "twój", "wyglad": "wygląd", "wartosci": "wartości",
  140. "usuniete": "usunięte", "usunieta": "usunięta", "usuniety": "usunięty",
  141. "kazdy": "każdy", "kazda": "każda", "kazdej": "każdej",
  142. "kazdego": "każdego", "dostawce": "dostawcę", "zostana": "zostaną",
  143. "zarzadzaj": "zarządzaj", "zarzadzac": "zarządzać",
  144. "zarzadzanie": "zarządzanie", "powiazane": "powiązane", "jesli": "jeśli",
  145. "potwierdzic": "potwierdzić", "potwierdz": "potwierdź",
  146. "stawke": "stawkę", "zdjecia": "zdjęcia", "udostepnij": "udostępnij",
  147. "udostepnione": "udostępnione", "prosze": "proszę",
  148. "wyswietl": "wyświetl", "wyswietlane": "wyświetlane",
  149. "wyswietlanie": "wyświetlanie", "wyswietlana": "wyświetlana",
  150. "wyswietlania": "wyświetlania", "odbierac": "odbierać",
  151. "oczekujace": "oczekujące", "nazwe": "nazwę", "ponizej": "poniżej",
  152. "przejdz": "przejdź", "rozpoczac": "rozpocząć", "znakow": "znaków",
  153. "otworz": "otwórz", "zastosowac": "zastosować", "pobrac": "pobrać",
  154. "ktorym": "którym", "ktore": "które", "nastepnie": "następnie",
  155. "nastepny": "następny", "nastepna": "następna", "sprobuj": "spróbuj",
  156. "firme": "firmę", "zmien": "zmień", "zmienic": "zmienić",
  157. "logowac": "logować", "zalogowac": "zalogować",
  158. "widocznosc": "widoczność", "beda": "będą", "bedzie": "będzie",
  159. "bedziesz": "będziesz", "pojawia": "pojawią", "anulowac": "anulować",
  160. "ilosc": "ilość", "sprzedazy": "sprzedaży", "sroda": "środa",
  161. "piatek": "piątek", "szablonow": "szablonów", "zadne": "żadne",
  162. "przegladac": "przeglądać", "przegladarka": "przeglądarka",
  163. "przegladarki": "przeglądarki", "wiecej": "więcej",
  164. "dziekujemy": "dziękujemy", "pojazdow": "pojazdów", "swoja": "swoją",
  165. "prog": "próg", "miec": "mieć", "ogolne": "ogólne",
  166. "zweryfikowac": "zweryfikować", "rowniez": "również",
  167. "skonfigurowac": "skonfigurować", "zezwolic": "zezwolić",
  168. "wymus": "wymuś", "historie": "historię", "sprawdz": "sprawdź",
  169. "przywroc": "przywróć", "pomoca": "pomocą",
  170. "bezpieczenstwa": "bezpieczeństwa", "straca": "stracą",
  171. "kolejnosc": "kolejność", "kwalifikujace": "kwalifikujące",
  172. "wygasniecie": "wygaśnięcie", "podkreslenia": "podkreślenia",
  173. "godzine": "godzinę", "sposob": "sposób", "wymagaja": "wymagają",
  174. "salgsvilkar": "salgsvilkår",
  175. },
  176. }
  177. def match_case(src: str, repl: str) -> str:
  178. """Keep the original capitalisation: Numero -> Numéro, NUMERO -> NUMÉRO."""
  179. if src.isupper():
  180. return repl.upper()
  181. if src[:1].isupper():
  182. return repl[:1].upper() + repl[1:]
  183. return repl
  184. def fix_text(text: str, table: dict[str, str], counts: dict[str, int]) -> str:
  185. def sub(m: re.Match) -> str:
  186. word = m.group(0)
  187. repl = table.get(word.lower())
  188. if repl is None:
  189. return word
  190. counts[word.lower()] = counts.get(word.lower(), 0) + 1
  191. return match_case(word, repl)
  192. pattern = re.compile(
  193. r"(?<![^\W\d_])(" + "|".join(sorted(map(re.escape, table), key=len, reverse=True)) + r")(?![^\W\d_])",
  194. re.IGNORECASE,
  195. )
  196. return pattern.sub(sub, text)
  197. def walk(node, table, counts):
  198. if isinstance(node, str):
  199. return fix_text(node, table, counts)
  200. if isinstance(node, dict):
  201. return {k: walk(v, table, counts) for k, v in node.items()}
  202. if isinstance(node, list):
  203. return [walk(v, table, counts) for v in node]
  204. return node
  205. def main(write: bool) -> None:
  206. grand = 0
  207. for locale, table in FIXES.items():
  208. counts: dict[str, int] = {}
  209. touched = 0
  210. for path in sorted((ROOT / locale).glob("*.json")):
  211. original = path.read_text(encoding="utf-8")
  212. data = json.loads(original)
  213. fixed = walk(data, table, counts)
  214. out = json.dumps(fixed, ensure_ascii=False, indent=2) + "\n"
  215. if json.dumps(data, ensure_ascii=False, indent=2) + "\n" != out:
  216. touched += 1
  217. if write:
  218. path.write_text(out, encoding="utf-8")
  219. total = sum(counts.values())
  220. grand += total
  221. top = sorted(counts.items(), key=lambda kv: -kv[1])[:6]
  222. print(f"{locale:6} {total:5} words restored across {touched:2} files "
  223. + ", ".join(f"{k}->{table[k]} x{v}" for k, v in top))
  224. print(f"\nTOTAL: {grand} words restored")
  225. if __name__ == "__main__":
  226. main(write="--write" in sys.argv)