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 "