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>
113 lines
4.8 KiB
Python
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}"
|