Add multilingual audit CI pipeline + extract mandelblog_content_guard
This commit is contained in:
368
mandelblog_content_guard/system_strings.py
Normal file
368
mandelblog_content_guard/system_strings.py
Normal file
@@ -0,0 +1,368 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
SYSTEM_STRING_SPECS = {
|
||||
"plan_badge": {
|
||||
"sources": ("PLAN",),
|
||||
"issue_type": "generic_badge_label",
|
||||
"translations": {
|
||||
"en": "Package",
|
||||
"fr": "FORFAIT",
|
||||
"es": "Paquete",
|
||||
"ru": "Пакет",
|
||||
},
|
||||
"canonical_by_locale": {
|
||||
"de": ("PLAN",),
|
||||
"nl": ("PLAN",),
|
||||
"it": ("PIANO",),
|
||||
},
|
||||
"contexts": {
|
||||
"en": {
|
||||
"badge": "Package",
|
||||
"label": "Package",
|
||||
"title": "Package",
|
||||
"heading": "Package",
|
||||
"rendered": "Package",
|
||||
},
|
||||
"fr": {
|
||||
"badge": "FORFAIT",
|
||||
"label": "FORFAIT",
|
||||
"title": "FORFAIT",
|
||||
"heading": "FORFAIT",
|
||||
"rendered": "FORFAIT",
|
||||
},
|
||||
"es": {
|
||||
"badge": "Paquete",
|
||||
"label": "Paquete",
|
||||
"title": "Paquete",
|
||||
"heading": "Paquete",
|
||||
"rendered": "Paquete",
|
||||
},
|
||||
"ru": {
|
||||
"badge": "Пакет",
|
||||
"label": "Пакет",
|
||||
"title": "Пакет",
|
||||
"heading": "Пакет",
|
||||
"rendered": "Пакет",
|
||||
},
|
||||
},
|
||||
},
|
||||
"services_badge": {
|
||||
"sources": ("SERVICES",),
|
||||
"issue_type": "generic_badge_label",
|
||||
"translations": {
|
||||
"en": "Services",
|
||||
"fr": "PRESTATIONS",
|
||||
"pt": "SERVIÇOS",
|
||||
},
|
||||
"contexts": {
|
||||
"en": {
|
||||
"badge": "Services",
|
||||
"label": "Services",
|
||||
"title": "Services",
|
||||
"heading": "Services",
|
||||
"rendered": "Services",
|
||||
},
|
||||
"fr": {
|
||||
"badge": "PRESTATIONS",
|
||||
"label": "PRESTATIONS",
|
||||
"title": "PRESTATIONS",
|
||||
"heading": "PRESTATIONS",
|
||||
"rendered": "PRESTATIONS",
|
||||
},
|
||||
"pt": {
|
||||
"badge": "SERVIÇOS",
|
||||
"label": "SERVIÇOS",
|
||||
"title": "SERVIÇOS",
|
||||
"heading": "SERVIÇOS",
|
||||
"rendered": "SERVIÇOS",
|
||||
},
|
||||
},
|
||||
},
|
||||
"response_time": {
|
||||
"sources": ("Reaktionszeit",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"en": "Response time",
|
||||
"fr": "Temps de réponse",
|
||||
"es": "Tiempo de respuesta",
|
||||
"it": "Tempo di risposta",
|
||||
"ru": "Время ответа",
|
||||
},
|
||||
},
|
||||
"average_delivery": {
|
||||
"sources": ("Durchschnittliche Lieferung",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"en": "Average delivery time",
|
||||
"fr": "Délai moyen de livraison",
|
||||
"es": "Plazo medio de entrega",
|
||||
"it": "Tempo medio di consegna",
|
||||
"ru": "Средний срок запуска",
|
||||
},
|
||||
},
|
||||
"without_commitment": {
|
||||
"sources": ("Unverbindlich",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"en": "No obligation",
|
||||
"fr": "Sans engagement",
|
||||
"es": "Sin compromiso",
|
||||
"it": "Senza impegno",
|
||||
"pt": "Sem compromisso",
|
||||
"ru": "Без обязательств",
|
||||
},
|
||||
},
|
||||
"transparent_label": {
|
||||
"sources": ("Transparent",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"en": "Clear",
|
||||
"fr": "Clair",
|
||||
"es": "Transparente",
|
||||
"it": "Chiaro",
|
||||
"pt": "Transparente",
|
||||
"ru": "Прозрачно",
|
||||
},
|
||||
"contexts": {
|
||||
"en": {
|
||||
"badge": "Clear",
|
||||
"label": "Clear",
|
||||
"metric": "Clear",
|
||||
"stat": "Clear",
|
||||
"rendered": "Clear",
|
||||
},
|
||||
"fr": {
|
||||
"badge": "Clair",
|
||||
"label": "Clair",
|
||||
"metric": "Clair",
|
||||
"stat": "Clair",
|
||||
"rendered": "Clair",
|
||||
},
|
||||
"es": {
|
||||
"badge": "Transparente",
|
||||
"label": "Transparente",
|
||||
"metric": "Transparente",
|
||||
"stat": "Transparente",
|
||||
"rendered": "Transparente",
|
||||
},
|
||||
"it": {
|
||||
"badge": "Chiaro",
|
||||
"label": "Chiaro",
|
||||
"metric": "Chiaro",
|
||||
"stat": "Chiaro",
|
||||
"rendered": "Chiaro",
|
||||
},
|
||||
"pt": {
|
||||
"badge": "Clara",
|
||||
"label": "Clara",
|
||||
"metric": "Investimento claro",
|
||||
"stat": "Investimento claro",
|
||||
"rendered": "Investimento claro",
|
||||
},
|
||||
"ru": {
|
||||
"badge": "Прозрачно",
|
||||
"label": "Прозрачно",
|
||||
"metric": "Прозрачно",
|
||||
"stat": "Прозрачно",
|
||||
"rendered": "Прозрачно",
|
||||
},
|
||||
},
|
||||
},
|
||||
"weeks_1_2": {
|
||||
"sources": ("1-2 Wochen",),
|
||||
"issue_type": "weak_marketing_copy",
|
||||
"translations": {
|
||||
"fr": "1 à 2 semaines",
|
||||
"es": "1-2 semanas",
|
||||
"it": "1-2 settimane",
|
||||
"pt": "1 a 2 semanas",
|
||||
},
|
||||
"contexts": {
|
||||
"fr": {
|
||||
"metric": "1 à 2 semaines",
|
||||
"stat": "1 à 2 semaines",
|
||||
},
|
||||
"es": {
|
||||
"metric": "1-2 semanas",
|
||||
"stat": "1-2 semanas",
|
||||
},
|
||||
"it": {
|
||||
"metric": "1-2 settimane",
|
||||
"stat": "1-2 settimane",
|
||||
},
|
||||
"pt": {
|
||||
"metric": "1 a 2 semanas",
|
||||
"stat": "1 a 2 semanas",
|
||||
},
|
||||
},
|
||||
},
|
||||
"weeks_2_4": {
|
||||
"sources": ("2-4 Wochen",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"fr": "2 à 4 semaines",
|
||||
},
|
||||
"contexts": {
|
||||
"fr": {
|
||||
"metric": "2 à 4 semaines",
|
||||
"stat": "2 à 4 semaines",
|
||||
},
|
||||
},
|
||||
},
|
||||
"days_label": {
|
||||
"sources": ("Tages",),
|
||||
"issue_type": "weak_marketing_copy",
|
||||
"translations": {
|
||||
"fr": "jours",
|
||||
"pt": "dias",
|
||||
},
|
||||
},
|
||||
"customer_reviews": {
|
||||
"sources": ("Kundenschätzung",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"en": "Customer rating",
|
||||
"fr": "Avis clients",
|
||||
"es": "Valoración de clientes",
|
||||
"it": "Valutazione clienti",
|
||||
"pt": "Avaliação dos clientes",
|
||||
"ru": "Оценка клиентов",
|
||||
},
|
||||
},
|
||||
"editable_label": {
|
||||
"sources": ("Bearbeitbar",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"en": "Editable",
|
||||
"fr": "Modifiable",
|
||||
"es": "Editable",
|
||||
"it": "Modificabile",
|
||||
"pt": "Editável",
|
||||
"ru": "Редактируемо",
|
||||
},
|
||||
},
|
||||
"core_pages_label": {
|
||||
"sources": ("Startseite + Kernseiten",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"pt": "Página inicial + páginas essenciais",
|
||||
},
|
||||
},
|
||||
"detailed_page_structure": {
|
||||
"sources": ("Detailliertes Seitenlayout",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"fr": "Structure détaillée des pages",
|
||||
"es": "Estructura detallada de páginas",
|
||||
"it": "Struttura dettagliata delle pagine",
|
||||
"pt": "Estrutura detalhada das páginas",
|
||||
"ru": "Детальная структура страниц",
|
||||
},
|
||||
},
|
||||
"business_process_cta": {
|
||||
"sources": ("Geschäftsprozess besprechen",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"fr": "Échanger sur votre processus métier",
|
||||
"es": "Hablar sobre el proceso del negocio",
|
||||
"pt": "Falar sobre o processo do negócio",
|
||||
},
|
||||
},
|
||||
"multilingual_rollout": {
|
||||
"sources": ("Mehrsprachige Einführung", "Mehrsprachiger Rollout-Plan"),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"fr": "Déploiement multilingue",
|
||||
"it": "Lancio multilingue",
|
||||
"ru": "Многоязычный запуск",
|
||||
},
|
||||
},
|
||||
"customization_integrations": {
|
||||
"sources": ("Anpassung & Integrationen",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"fr": "Personnalisation & intégrations",
|
||||
"es": "Personalización e integraciones",
|
||||
"it": "Personalizzazioni e integrazioni",
|
||||
"pt": "Personalização e integrações",
|
||||
"ru": "Настройка и интеграции",
|
||||
},
|
||||
},
|
||||
"transparent_investment": {
|
||||
"sources": ("Transparente Investition",),
|
||||
"issue_type": "foreign_ui_label",
|
||||
"translations": {
|
||||
"de": "Transparente Investition",
|
||||
"en": "Transparent pricing",
|
||||
"fr": "Investissement transparent",
|
||||
"es": "Inversión transparente",
|
||||
"it": "Investimento trasparente",
|
||||
"pt": "Investimento transparente",
|
||||
"ru": "Прозрачный бюджет",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_system_vocabulary(locale_code: str, keys: Iterable[str] | None = None) -> dict[str, str]:
|
||||
vocabulary: dict[str, str] = {}
|
||||
selected_keys = tuple(keys or SYSTEM_STRING_SPECS.keys())
|
||||
for key in selected_keys:
|
||||
spec = SYSTEM_STRING_SPECS[key]
|
||||
target = spec.get("translations", {}).get(locale_code)
|
||||
if not target:
|
||||
continue
|
||||
for source in spec["sources"]:
|
||||
vocabulary[source] = target
|
||||
return vocabulary
|
||||
|
||||
|
||||
def build_contextual_system_vocabulary(locale_code: str, keys: Iterable[str] | None = None) -> dict[str, dict[str, str]]:
|
||||
contextual: dict[str, dict[str, str]] = {}
|
||||
selected_keys = tuple(keys or SYSTEM_STRING_SPECS.keys())
|
||||
for key in selected_keys:
|
||||
spec = SYSTEM_STRING_SPECS[key]
|
||||
locale_contexts = spec.get("contexts", {}).get(locale_code, {})
|
||||
if not locale_contexts:
|
||||
continue
|
||||
source = spec["sources"][0]
|
||||
for context_name, replacement in locale_contexts.items():
|
||||
contextual.setdefault(context_name, {})[source] = replacement
|
||||
return contextual
|
||||
|
||||
|
||||
def build_system_rewrite_candidates(keys: Iterable[str] | None = None) -> dict[str, str]:
|
||||
candidates: dict[str, str] = {}
|
||||
selected_keys = tuple(keys or SYSTEM_STRING_SPECS.keys())
|
||||
for key in selected_keys:
|
||||
spec = SYSTEM_STRING_SPECS[key]
|
||||
for source in spec["sources"]:
|
||||
candidates[source] = spec["issue_type"]
|
||||
return candidates
|
||||
|
||||
|
||||
def all_system_sources() -> set[str]:
|
||||
sources: set[str] = set()
|
||||
for spec in SYSTEM_STRING_SPECS.values():
|
||||
sources.update(spec["sources"])
|
||||
return sources
|
||||
|
||||
|
||||
def is_canonical_system_string(locale_code: str, source: str) -> bool:
|
||||
for spec in SYSTEM_STRING_SPECS.values():
|
||||
if source in spec.get("canonical_by_locale", {}).get(locale_code, ()):
|
||||
return True
|
||||
if locale_code == "de":
|
||||
return source in all_system_sources()
|
||||
replacement = system_string_replacement(locale_code, source)
|
||||
return bool(replacement and replacement == source)
|
||||
|
||||
|
||||
def system_string_replacement(locale_code: str, source: str) -> str:
|
||||
for spec in SYSTEM_STRING_SPECS.values():
|
||||
if source not in spec["sources"]:
|
||||
continue
|
||||
return spec.get("translations", {}).get(locale_code, "")
|
||||
return ""
|
||||
Reference in New Issue
Block a user