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>
This commit is contained in:
112
web/tests/test_i18n_parity.py
Normal file
112
web/tests/test_i18n_parity.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
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}"
|
||||
Reference in New Issue
Block a user