Files
padelnomics/web/tests/test_i18n_parity.py
Deeman 4ae00b35d1 refactor: flatten padelnomics/padelnomics/ → repo root
git mv all tracked files from the nested padelnomics/ workspace
directory to the git repo root. Merged .gitignore files.
No code changes — pure path rename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 00:44:40 +01:00

113 lines
4.8 KiB
Python

"""
Tests that EN and DE locale files are consistent.
"""
from padelnomics.i18n import _CALC_ITEM_NAMES, _TRANSLATIONS, SUPPORTED_LANGS
# Keys with identical EN/DE values are acceptable — financial abbreviations,
# month abbreviations, English brand names used in German, and UI chrome
# that intentionally stays English in DE (Dashboard, Admin, Feedback).
_IDENTICAL_VALUE_ALLOWLIST = {
# Financial / technical abbreviations (same in DE)
"card_cash_on_cash", "card_irr", "card_moic", "card_rev_pah",
"th_dscr", "th_ebitda", "wf_moic", "wiz_capex", "wiz_irr",
# Month abbreviations (same in DE: Jan, Feb, Apr, Jun, Jul, Aug, Sep, Nov)
"month_jan", "month_feb", "month_apr", "month_jun",
"month_jul", "month_aug", "month_sep", "month_nov",
# Indoor / Outdoor (English terms used in DE)
"label_indoor", "label_outdoor", "toggle_indoor", "toggle_outdoor",
"features_card_3_h2", "landing_feature_3_h3",
"q1_facility_indoor", "q1_facility_outdoor", "q1_facility_both",
# Revenue streams and product labels (English terms used in DE)
"stream_coaching", "stream_fb",
"pill_light_led_standard", "q1_lighting_led_std",
# Plan names (brand names, same in DE)
"sup_basic_name", "sup_growth_name", "sup_pro_name",
"suppliers_basic_name", "suppliers_growth_name", "suppliers_pro_name",
"dir_card_featured", "dir_card_growth", "dir_cat_software",
# Supplier boost option names
"sup_boost_logo", "sup_boost_sticky", "sup_boosts_h3",
# Comparison table headers that use English terms in DE
"sup_cmp_th_ads", "sup_cmp_th_us",
# Problem section heading — "Google Ads" is a brand name
"sup_prob_ads_h3", "suppliers_problem_2_h3",
# Budget/lead labels that are English in DE
"sup_lead_budget", "suppliers_leads_budget",
# UI chrome that stays English in DE
"nav_admin", "nav_dashboard", "nav_feedback",
# Country names that are the same
"country_uk", "country_us",
"dir_country_CN", "dir_country_PT", "dir_country_TH",
# Optional annotation
"q9_company_note",
# Dashboard chrome that stays English in DE (brand term)
"dash_page_title", "dash_h1", "dash_name_label",
# Supplier dashboard — analytics brand name
"sd_ov_via_umami",
# Lead heat filter labels (English terms used in DE)
"sd_leads_filter_hot", "sd_leads_filter_warm", "sd_leads_filter_cool",
# "Budget", "Name", "Phase", "Investor" — same in both languages
"sd_leads_budget", "sd_card_budget", "sd_unlocked_label_budget",
"sd_unlocked_label_name", "sd_unlocked_label_phase", "sd_stakeholder_investor",
# Listing form labels that are English brand terms / same in DE
"sd_lst_logo", "sd_lst_website",
# Boost option name — "Logo" is the same in DE
"sd_boost_logo_name",
# Business plan — Indoor/Outdoor same in DE, financial abbreviations
"bp_indoor", "bp_outdoor",
"bp_lbl_ebitda", "bp_lbl_irr", "bp_lbl_moic", "bp_lbl_opex",
}
def test_en_de_key_parity():
"""EN and DE must have identical key sets."""
en_keys = set(_TRANSLATIONS["en"].keys())
de_keys = set(_TRANSLATIONS["de"].keys())
en_only = en_keys - de_keys
de_only = de_keys - en_keys
assert not en_only, f"Keys in EN but not DE: {sorted(en_only)}"
assert not de_only, f"Keys in DE but not EN: {sorted(de_only)}"
def test_all_values_non_empty():
"""No translation value should be an empty string."""
for lang in SUPPORTED_LANGS:
for key, value in _TRANSLATIONS[lang].items():
assert value, f"Empty value for key {key!r} in lang {lang!r}"
def test_no_untranslated_copy_paste():
"""No key should have an identical EN and DE value (catches missed translations).
Some keys are exempt — see _IDENTICAL_VALUE_ALLOWLIST.
"""
en = _TRANSLATIONS["en"]
de = _TRANSLATIONS["de"]
violations = []
for key in en:
if key in _IDENTICAL_VALUE_ALLOWLIST:
continue
if en[key] == de[key]:
violations.append((key, en[key]))
assert not violations, (
"Keys with identical EN/DE values (likely untranslated): "
+ ", ".join(f"{k!r}={v!r}" for k, v in violations[:10])
+ (f" ... and {len(violations) - 10} more" if len(violations) > 10 else "")
)
def test_calc_item_names_key_parity():
"""EN and DE calc item names must have identical key sets."""
en_keys = set(_CALC_ITEM_NAMES["en"].keys())
de_keys = set(_CALC_ITEM_NAMES["de"].keys())
assert en_keys == de_keys, (
f"Calc item key mismatch — "
f"EN-only: {sorted(en_keys - de_keys)}, DE-only: {sorted(de_keys - en_keys)}"
)
def test_calc_item_names_non_empty():
"""No calc item name should be empty."""
for lang in SUPPORTED_LANGS:
for key, value in _CALC_ITEM_NAMES[lang].items():
assert value, f"Empty calc item name for key {key!r} in lang {lang!r}"