From c4ce86bd64fa36039b46a15d8185d268d42ae214 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 13 May 2026 15:11:30 +0000 Subject: [PATCH] feat(i18n): add language-pack scaffold with English and Spanish Introduces ``src/i18n`` with a tiny JSON-backed t() lookup, an in-session language preference, and a sidebar selector wired through ``hide_streamlit_chrome`` so every page picks up the same picker. Covers home, tool cards, findings panel, gate, shutdown, and pickup banner strings. Tests pin pack parity and the farewell-overlay JS escape so future packs can't silently regress. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/gui/app.py | 26 ++--- src/gui/components/_legacy.py | 143 +++++++++++++++++----------- src/gui/pages/99_Close.py | 15 ++- src/gui/tools_registry.py | 17 ++++ src/i18n/__init__.py | 155 ++++++++++++++++++++++++++++++ src/i18n/packs/en.json | 97 +++++++++++++++++++ src/i18n/packs/es.json | 97 +++++++++++++++++++ tests/test_lang_packs.py | 174 ++++++++++++++++++++++++++++++++++ 8 files changed, 649 insertions(+), 75 deletions(-) create mode 100644 src/i18n/__init__.py create mode 100644 src/i18n/packs/en.json create mode 100644 src/i18n/packs/es.json create mode 100644 tests/test_lang_packs.py diff --git a/src/gui/app.py b/src/gui/app.py index 2ace7bf..4378411 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -26,13 +26,16 @@ from src.gui.components import ( hide_streamlit_chrome, upload_and_analyze_section, ) +from src.i18n import t st.set_page_config( - page_title="DataTools — Data Cleaning Mastery", + page_title=t("home.page_title"), page_icon="🧹", layout="wide", ) +# ``hide_streamlit_chrome`` also renders the sidebar language selector, +# so every page that hides chrome picks up the same picker. hide_streamlit_chrome() @@ -40,8 +43,8 @@ hide_streamlit_chrome() # Home page # --------------------------------------------------------------------------- -st.title("🧹 DataTools — Data Cleaning Mastery") -st.caption("A 9-tool suite for cleaning, standardizing, and validating tabular data. Runs 100% locally.") +st.title(t("home.title")) +st.caption(t("home.caption")) st.divider() @@ -57,7 +60,7 @@ st.divider() # Tool cards # --------------------------------------------------------------------------- -from src.gui.tools_registry import TOOLS +from src.gui.tools_registry import TOOLS, tool_description, tool_name # Render tool cards in a 3-column grid. Cards picked up by the analyzer get a # coloured "N findings" badge so the user can see at a glance which tools @@ -70,15 +73,17 @@ for row_start in range(0, len(TOOLS), 3): break tool = TOOLS[idx] with col: + status_key = "status.ready" if tool.status == "Ready" else "status.coming_soon" status_color = "green" if tool.status == "Ready" else "orange" badge = "" n = findings_count_for_tool(tool.tool_id) if n: - badge = f" :red-background[**{n} finding{'s' if n != 1 else ''}**]" + badge_key = "home.findings_badge_one" if n == 1 else "home.findings_badge_other" + badge = f" :red-background[**{t(badge_key, n=n)}**]" st.markdown( - f"### {tool.icon} {tool.name}{badge}\n\n" - f"{tool.description}\n\n" - f":{status_color}[**{tool.status}**]" + f"### {tool.icon} {tool_name(tool.tool_id)}{badge}\n\n" + f"{tool_description(tool.tool_id)}\n\n" + f":{status_color}[**{t(status_key)}**]" ) @@ -87,7 +92,4 @@ for row_start in range(0, len(TOOLS), 3): # --------------------------------------------------------------------------- st.divider() -st.caption( - "Runs locally. Your data never leaves this computer. " - "| DataTools v3.0" -) +st.caption(t("chrome.footer")) diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index 0317995..7defbb3 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -11,6 +11,7 @@ from typing import Optional import pandas as pd import streamlit as st +from src.i18n import t as _t from src.core.dedup import ( Algorithm, ColumnMatchStrategy, @@ -72,15 +73,26 @@ footer { def hide_streamlit_chrome() -> None: - """Inject CSS to hide Streamlit's default header, menu, and footer.""" + """Inject CSS to hide Streamlit's default header, menu, and footer. + + Also renders the sidebar language selector, since every entrypoint + that hides the default chrome wants the picker visible in the + same place. Pages that want a clean chrome without the selector can + inject ``_HIDE_CHROME_CSS`` themselves instead of calling this. + """ st.markdown(_HIDE_CHROME_CSS, unsafe_allow_html=True) + # Imported lazily so this module stays importable in environments + # where the i18n packs haven't been laid out (e.g. unit tests of + # individual legacy helpers). + from src.i18n import render_language_selector + render_language_selector() # --------------------------------------------------------------------------- # Clean shutdown # --------------------------------------------------------------------------- -_FAREWELL_SCRIPT = """ +_FAREWELL_SCRIPT_TEMPLATE = """ ") + # Every single-quote must be backslash-escaped so it can't + # terminate the JS string literal that wraps the payload. + assert "\\'" in out + assert "'" not in out.replace("\\'", "") + assert "