feat(nav,i18n): sticky footer with Back-to-Home + localized tool headers
Two unrelated UX issues addressed in one sweep across all nine tool
pages because they share the same edit surface.
(1) Sticky footer replaces the top + bottom back-link buttons.
Reported: a big white empty footer space at the bottom of every page;
the Back to Home button at the top scrolled out of view on long pages.
New ``render_sticky_footer()`` helper in ``components/_legacy.py``
injects a fixed-position bar at ``bottom: 0`` of the viewport with:
- A border-top so it visually reads as a non-movable bar.
- A semi-transparent background (rgba 0.96 + ``backdrop-filter: blur``)
so content underneath shows through faintly when the user scrolls.
- A styled ``<a href="home">`` anchor (not an ``st.button``) because
Streamlit widgets can't be CSS-positioned reliably — Streamlit owns
the widget's DOM container and re-mounts it on every rerun. A real
anchor sits exactly where the CSS puts it and triggers Streamlit's
URL routing to the home page.
- ``padding-bottom: 3.5rem`` on the main container so the last widget
isn't hidden behind the bar.
Called once per tool page, immediately after ``hide_streamlit_chrome()``
so it renders even on pages that ``st.stop()`` early before any other
content runs. The old top-and-bottom ``back_to_home_link()`` calls are
removed from every tool page; their entry/exit points were dropping
the button when the script short-circuited.
(2) Tool-page headers now localize.
Reported: switching the sidebar language picker to Spanish left the
tool page's title + caption in English. Root cause: every page had
hard-coded ``st.title("✂️ Clean Text")`` / ``st.caption("Trim
whitespace...")`` strings.
Added per-tool ``tools.<id>.page_title`` and
``tools.<id>.page_caption`` keys to ``en.json`` and ``es.json`` for
all nine tools. Routed each page's title/caption call through ``t()``.
Verified: with ``ui_lang=es`` set, the Clean Text page now renders
"✂️ Limpiar texto" + the Spanish caption.
Updated ``tests/gui/test_smoke.py::EXPECTED_SUBSTRINGS`` so the
``es`` column for each tool page asserts the actual Spanish string
(was a duplicate of the English string back when the page bodies
were English-only).
2220 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,7 @@ from .activation import ( # noqa: F401 re-exported
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
# Shared chrome / pickup
|
# Shared chrome / pickup
|
||||||
"back_to_home_link",
|
"back_to_home_link",
|
||||||
|
"render_sticky_footer",
|
||||||
"hide_streamlit_chrome",
|
"hide_streamlit_chrome",
|
||||||
"html_download_button",
|
"html_download_button",
|
||||||
"local_download_button",
|
"local_download_button",
|
||||||
|
|||||||
@@ -491,6 +491,88 @@ def local_download_button(
|
|||||||
html_download_button = local_download_button
|
html_download_button = local_download_button
|
||||||
|
|
||||||
|
|
||||||
|
def render_sticky_footer() -> None:
|
||||||
|
"""Render a slim fixed-position footer at the bottom of the viewport.
|
||||||
|
|
||||||
|
Contains a "Back to Home" link that's always visible regardless of
|
||||||
|
scroll position. Replaces the previous top + bottom-of-page
|
||||||
|
``back_to_home_link`` buttons: a single sticky bar is more
|
||||||
|
discoverable, doesn't scroll out of view, and visually reads as a
|
||||||
|
"non-movable footer" (border-top + slim padding) rather than
|
||||||
|
just-another-button-at-the-bottom.
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
|
||||||
|
- Uses a styled ``<a href="home">`` anchor rather than an
|
||||||
|
``st.button`` because Streamlit widgets can't be CSS-positioned
|
||||||
|
reliably (their DOM container is owned by Streamlit's renderer
|
||||||
|
and gets re-mounted on every rerun). A real anchor sits anywhere
|
||||||
|
we want and triggers Streamlit's URL routing to the home page.
|
||||||
|
- ``href="home"`` is a relative path so it works behind a reverse
|
||||||
|
proxy / non-root mount.
|
||||||
|
- We bump ``padding-bottom`` on the main container so the last
|
||||||
|
widget isn't hidden behind the fixed bar.
|
||||||
|
- Theme: ``rgba(255,255,255,0.96)`` background with a thin
|
||||||
|
semi-transparent border-top. The blur backdrop keeps content
|
||||||
|
slightly visible underneath. Works against both light and dark
|
||||||
|
Streamlit themes — explicit colours, not theme variables,
|
||||||
|
because Streamlit's CSS variable surface is unstable across
|
||||||
|
minor versions.
|
||||||
|
"""
|
||||||
|
import html as _html
|
||||||
|
label = _html.escape(_t("nav.back_to_home"))
|
||||||
|
st.markdown(
|
||||||
|
f"""
|
||||||
|
<style>
|
||||||
|
[data-testid="stAppViewBlockContainer"] {{
|
||||||
|
padding-bottom: 3.5rem !important;
|
||||||
|
}}
|
||||||
|
.datatools-sticky-footer {{
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
border-top: 1px solid rgba(49, 51, 63, 0.22);
|
||||||
|
padding: 0.45rem 1.25rem;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
font-family: inherit;
|
||||||
|
}}
|
||||||
|
.datatools-sticky-footer a {{
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(38, 39, 48);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.3rem 0.85rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid rgba(49, 51, 63, 0.22);
|
||||||
|
background: rgb(240, 242, 246);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s ease, border-color 0.12s ease;
|
||||||
|
}}
|
||||||
|
.datatools-sticky-footer a:hover {{
|
||||||
|
background: rgb(225, 228, 235);
|
||||||
|
border-color: rgba(49, 51, 63, 0.35);
|
||||||
|
}}
|
||||||
|
.datatools-sticky-footer a:active {{
|
||||||
|
background: rgb(210, 214, 222);
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
<div class="datatools-sticky-footer">
|
||||||
|
<a href="home" target="_self">{label}</a>
|
||||||
|
</div>
|
||||||
|
""",
|
||||||
|
unsafe_allow_html=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def back_to_home_link(*, key: str = "_back_to_home_link") -> None:
|
def back_to_home_link(*, key: str = "_back_to_home_link") -> None:
|
||||||
"""Render a "← Back to Home" affordance on a tool page.
|
"""Render a "← Back to Home" affordance on a tool page.
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from src.core.io import read_file, list_sheets, detect_encoding, detect_delimite
|
|||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
apply_review_decisions,
|
apply_review_decisions,
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
config_panel,
|
config_panel,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
html_download_button,
|
html_download_button,
|
||||||
@@ -27,10 +28,11 @@ from src.gui.components import (
|
|||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
results_summary,
|
results_summary,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.DEDUPLICATOR)
|
require_feature_or_render_upgrade(FeatureFlag.DEDUPLICATOR)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -55,8 +57,8 @@ for key, default in _DEFAULTS.items():
|
|||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("🔍 Find Duplicates")
|
st.title(t("tools.01_deduplicator.page_title"))
|
||||||
st.caption("Find and remove duplicate rows in CSV, delimited text, and Excel files.")
|
st.caption(t("tools.01_deduplicator.page_caption"))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -399,8 +401,6 @@ else:
|
|||||||
# Footer
|
# Footer
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption(
|
st.caption(
|
||||||
"Runs locally. Your data never leaves this computer. "
|
"Runs locally. Your data never leaves this computer. "
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
html_download_button,
|
html_download_button,
|
||||||
pickup_or_upload,
|
pickup_or_upload,
|
||||||
render_hidden_aware_preview,
|
render_hidden_aware_preview,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
from src.core.text_clean import (
|
from src.core.text_clean import (
|
||||||
PRESETS,
|
PRESETS,
|
||||||
@@ -32,7 +34,7 @@ from src.core.text_clean import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.TEXT_CLEANER)
|
require_feature_or_render_upgrade(FeatureFlag.TEXT_CLEANER)
|
||||||
|
|
||||||
|
|
||||||
@@ -40,11 +42,8 @@ require_feature_or_render_upgrade(FeatureFlag.TEXT_CLEANER)
|
|||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("✂️ Clean Text")
|
st.title(t("tools.02_text_cleaner.page_title"))
|
||||||
st.caption(
|
st.caption(t("tools.02_text_cleaner.page_caption"))
|
||||||
"Trim whitespace, fold smart quotes, strip invisible characters, and "
|
|
||||||
"normalize line endings. Runs locally — your data never leaves this computer."
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# File upload
|
# File upload
|
||||||
@@ -379,8 +378,6 @@ with dl_c:
|
|||||||
mime="application/json",
|
mime="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
html_download_button,
|
html_download_button,
|
||||||
pickup_or_upload,
|
pickup_or_upload,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.core.format_standardize import (
|
from src.core.format_standardize import (
|
||||||
PRESETS,
|
PRESETS,
|
||||||
FieldType,
|
FieldType,
|
||||||
@@ -30,7 +32,7 @@ from src.core.format_standardize import (
|
|||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.FORMAT_STANDARDIZER)
|
require_feature_or_render_upgrade(FeatureFlag.FORMAT_STANDARDIZER)
|
||||||
|
|
||||||
|
|
||||||
@@ -38,12 +40,8 @@ require_feature_or_render_upgrade(FeatureFlag.FORMAT_STANDARDIZER)
|
|||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("📐 Standardize Formats")
|
st.title(t("tools.03_format_standardizer.page_title"))
|
||||||
st.caption(
|
st.caption(t("tools.03_format_standardizer.page_caption"))
|
||||||
"Canonicalize dates, phone numbers, currency, names, addresses, and "
|
|
||||||
"booleans on a per-column basis. Runs locally — your data never leaves "
|
|
||||||
"this computer."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -650,8 +648,6 @@ with dl_c:
|
|||||||
mime="application/json",
|
mime="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
html_download_button,
|
html_download_button,
|
||||||
pickup_or_upload,
|
pickup_or_upload,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.core.missing import (
|
from src.core.missing import (
|
||||||
DEFAULT_SENTINELS,
|
DEFAULT_SENTINELS,
|
||||||
MissingOptions,
|
MissingOptions,
|
||||||
@@ -31,7 +33,7 @@ from src.core.missing import (
|
|||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.MISSING_HANDLER)
|
require_feature_or_render_upgrade(FeatureFlag.MISSING_HANDLER)
|
||||||
|
|
||||||
|
|
||||||
@@ -39,11 +41,8 @@ require_feature_or_render_upgrade(FeatureFlag.MISSING_HANDLER)
|
|||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("🕳️ Fix Missing Values")
|
st.title(t("tools.04_missing_handler.page_title"))
|
||||||
st.caption(
|
st.caption(t("tools.04_missing_handler.page_caption"))
|
||||||
"Detect disguised nulls, profile missingness, and apply imputation or "
|
|
||||||
"drop strategies. Runs locally — your data never leaves this computer."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -412,8 +411,6 @@ with dl_c:
|
|||||||
mime="application/json",
|
mime="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
html_download_button,
|
html_download_button,
|
||||||
pickup_or_upload,
|
pickup_or_upload,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.core.column_mapper import (
|
from src.core.column_mapper import (
|
||||||
MapOptions,
|
MapOptions,
|
||||||
PRESETS,
|
PRESETS,
|
||||||
@@ -32,7 +34,7 @@ from src.core.column_mapper import (
|
|||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.COLUMN_MAPPER)
|
require_feature_or_render_upgrade(FeatureFlag.COLUMN_MAPPER)
|
||||||
|
|
||||||
|
|
||||||
@@ -40,11 +42,8 @@ require_feature_or_render_upgrade(FeatureFlag.COLUMN_MAPPER)
|
|||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("🗂️ Map Columns")
|
st.title(t("tools.05_column_mapper.page_title"))
|
||||||
st.caption(
|
st.caption(t("tools.05_column_mapper.page_caption"))
|
||||||
"Rename columns, enforce a target schema, and coerce types. Runs locally — "
|
|
||||||
"your data never leaves this computer."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -456,8 +455,6 @@ with dl_c:
|
|||||||
mime="application/json",
|
mime="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,23 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.OUTLIER_DETECTOR)
|
require_feature_or_render_upgrade(FeatureFlag.OUTLIER_DETECTOR)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("📊 Find Unusual Values")
|
st.title(t("tools.06_outlier_detector.page_title"))
|
||||||
st.caption("Detect and handle outliers in numeric columns.")
|
st.caption(t("tools.06_outlier_detector.page_caption"))
|
||||||
|
|
||||||
st.info("This tool is under development.")
|
st.info("This tool is under development.")
|
||||||
|
|
||||||
@@ -96,10 +98,9 @@ st.button("Detect Outliers", type="primary", use_container_width=True, disabled=
|
|||||||
# Footer
|
# Footer
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption(
|
st.caption(
|
||||||
"Runs locally. Your data never leaves this computer. "
|
"Runs locally. Your data never leaves this computer. "
|
||||||
"| DataTools v3.0"
|
"| DataTools v3.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,23 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.MULTI_FILE_MERGER)
|
require_feature_or_render_upgrade(FeatureFlag.MULTI_FILE_MERGER)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("📎 Combine Files")
|
st.title(t("tools.07_multi_file_merger.page_title"))
|
||||||
st.caption("Combine multiple CSV and Excel files into one dataset.")
|
st.caption(t("tools.07_multi_file_merger.page_caption"))
|
||||||
|
|
||||||
st.info("This tool is under development.")
|
st.info("This tool is under development.")
|
||||||
|
|
||||||
@@ -94,10 +96,9 @@ st.button("Merge Files", type="primary", use_container_width=True, disabled=True
|
|||||||
# Footer
|
# Footer
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption(
|
st.caption(
|
||||||
"Runs locally. Your data never leaves this computer. "
|
"Runs locally. Your data never leaves this computer. "
|
||||||
"| DataTools v3.0"
|
"| DataTools v3.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,23 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.VALIDATOR_REPORTER)
|
require_feature_or_render_upgrade(FeatureFlag.VALIDATOR_REPORTER)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("✅ Quality Check")
|
st.title(t("tools.08_validator_reporter.page_title"))
|
||||||
st.caption("Validate data against rules and generate quality reports.")
|
st.caption(t("tools.08_validator_reporter.page_caption"))
|
||||||
|
|
||||||
st.info("This tool is under development.")
|
st.info("This tool is under development.")
|
||||||
|
|
||||||
@@ -101,10 +103,9 @@ st.button("Validate & Generate Report", type="primary", use_container_width=True
|
|||||||
# Footer
|
# Footer
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption(
|
st.caption(
|
||||||
"Runs locally. Your data never leaves this computer. "
|
"Runs locally. Your data never leaves this computer. "
|
||||||
"| DataTools v3.0"
|
"| DataTools v3.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ if str(_project_root) not in sys.path:
|
|||||||
|
|
||||||
from src.gui.components import (
|
from src.gui.components import (
|
||||||
back_to_home_link,
|
back_to_home_link,
|
||||||
|
render_sticky_footer,
|
||||||
hide_streamlit_chrome,
|
hide_streamlit_chrome,
|
||||||
html_download_button,
|
html_download_button,
|
||||||
pickup_or_upload,
|
pickup_or_upload,
|
||||||
require_feature_or_render_upgrade,
|
require_feature_or_render_upgrade,
|
||||||
)
|
)
|
||||||
|
from src.i18n import t
|
||||||
from src.core.pipeline import (
|
from src.core.pipeline import (
|
||||||
Pipeline,
|
Pipeline,
|
||||||
SOFT_DEPENDENCIES,
|
SOFT_DEPENDENCIES,
|
||||||
@@ -33,7 +35,7 @@ from src.core.pipeline import (
|
|||||||
from src.license import FeatureFlag
|
from src.license import FeatureFlag
|
||||||
|
|
||||||
hide_streamlit_chrome()
|
hide_streamlit_chrome()
|
||||||
back_to_home_link()
|
render_sticky_footer()
|
||||||
require_feature_or_render_upgrade(FeatureFlag.PIPELINE_RUNNER)
|
require_feature_or_render_upgrade(FeatureFlag.PIPELINE_RUNNER)
|
||||||
|
|
||||||
|
|
||||||
@@ -41,11 +43,8 @@ require_feature_or_render_upgrade(FeatureFlag.PIPELINE_RUNNER)
|
|||||||
# Header
|
# Header
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
st.title("⚙️ Automated Workflows")
|
st.title(t("tools.09_pipeline_runner.page_title"))
|
||||||
st.caption(
|
st.caption(t("tools.09_pipeline_runner.page_caption"))
|
||||||
"Chain DataTools cleaning steps into one repeatable workflow. The "
|
|
||||||
"pipeline recommends an order; you stay in control."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -414,8 +413,6 @@ with dl_c:
|
|||||||
mime="application/json",
|
mime="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
back_to_home_link(key="_back_to_home_link_bottom")
|
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
st.caption("Runs locally. Your data never leaves this computer. | DataTools v3.0")
|
||||||
|
|
||||||
|
|||||||
@@ -106,39 +106,57 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"01_deduplicator": {
|
"01_deduplicator": {
|
||||||
"name": "Find Duplicates",
|
"name": "Find Duplicates",
|
||||||
"description": "Fuzzy matching, normalization, survivor selection, and interactive review."
|
"description": "Fuzzy matching, normalization, survivor selection, and interactive review.",
|
||||||
|
"page_title": "🔍 Find Duplicates",
|
||||||
|
"page_caption": "Find and remove duplicate rows in CSV, delimited text, and Excel files."
|
||||||
},
|
},
|
||||||
"02_text_cleaner": {
|
"02_text_cleaner": {
|
||||||
"name": "Clean Text",
|
"name": "Clean Text",
|
||||||
"description": "Whitespace trim, multi-space collapse, Unicode normalization, BOM and line-ending handling."
|
"description": "Whitespace trim, multi-space collapse, Unicode normalization, BOM and line-ending handling.",
|
||||||
|
"page_title": "✂️ Clean Text",
|
||||||
|
"page_caption": "Trim whitespace, fold smart quotes, strip invisible characters, and normalize line endings. Runs locally — your data never leaves this computer."
|
||||||
},
|
},
|
||||||
"03_format_standardizer": {
|
"03_format_standardizer": {
|
||||||
"name": "Standardize Formats",
|
"name": "Standardize Formats",
|
||||||
"description": "Standardize dates, currencies, names, phone numbers, and addresses."
|
"description": "Standardize dates, currencies, names, phone numbers, and addresses.",
|
||||||
|
"page_title": "📐 Standardize Formats",
|
||||||
|
"page_caption": "Canonicalize dates, phone numbers, currency, names, addresses, and booleans on a per-column basis. Runs locally — your data never leaves this computer."
|
||||||
},
|
},
|
||||||
"04_missing_handler": {
|
"04_missing_handler": {
|
||||||
"name": "Fix Missing Values",
|
"name": "Fix Missing Values",
|
||||||
"description": "Detect disguised nulls, missingness analysis, and imputation strategies."
|
"description": "Detect disguised nulls, missingness analysis, and imputation strategies.",
|
||||||
|
"page_title": "🕳️ Fix Missing Values",
|
||||||
|
"page_caption": "Detect disguised nulls, profile missingness, and apply imputation or drop strategies. Runs locally — your data never leaves this computer."
|
||||||
},
|
},
|
||||||
"05_column_mapper": {
|
"05_column_mapper": {
|
||||||
"name": "Map Columns",
|
"name": "Map Columns",
|
||||||
"description": "Rename columns, enforce a target schema, and coerce types."
|
"description": "Rename columns, enforce a target schema, and coerce types.",
|
||||||
|
"page_title": "🗂️ Map Columns",
|
||||||
|
"page_caption": "Rename columns, enforce a target schema, and coerce types. Runs locally — your data never leaves this computer."
|
||||||
},
|
},
|
||||||
"06_outlier_detector": {
|
"06_outlier_detector": {
|
||||||
"name": "Find Unusual Values",
|
"name": "Find Unusual Values",
|
||||||
"description": "Z-score, IQR, and MAD detection with domain-rule violations and winsorization."
|
"description": "Z-score, IQR, and MAD detection with domain-rule violations and winsorization.",
|
||||||
|
"page_title": "📊 Find Unusual Values",
|
||||||
|
"page_caption": "Detect and handle outliers in numeric columns."
|
||||||
},
|
},
|
||||||
"07_multi_file_merger": {
|
"07_multi_file_merger": {
|
||||||
"name": "Combine Files",
|
"name": "Combine Files",
|
||||||
"description": "Combine multiple CSV/Excel files with schema alignment."
|
"description": "Combine multiple CSV/Excel files with schema alignment.",
|
||||||
|
"page_title": "📎 Combine Files",
|
||||||
|
"page_caption": "Combine multiple CSV and Excel files into one dataset."
|
||||||
},
|
},
|
||||||
"08_validator_reporter": {
|
"08_validator_reporter": {
|
||||||
"name": "Quality Check",
|
"name": "Quality Check",
|
||||||
"description": "Validate against rules and generate PDF/Excel quality reports."
|
"description": "Validate against rules and generate PDF/Excel quality reports.",
|
||||||
|
"page_title": "✅ Quality Check",
|
||||||
|
"page_caption": "Validate data against rules and generate quality reports."
|
||||||
},
|
},
|
||||||
"09_pipeline_runner": {
|
"09_pipeline_runner": {
|
||||||
"name": "Automated Workflows",
|
"name": "Automated Workflows",
|
||||||
"description": "Chain tools in recommended order and pass output between steps."
|
"description": "Chain tools in recommended order and pass output between steps.",
|
||||||
|
"page_title": "⚙️ Automated Workflows",
|
||||||
|
"page_caption": "Chain DataTools cleaning steps into one repeatable workflow. The pipeline recommends an order; you stay in control."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
|
|||||||
@@ -106,39 +106,57 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"01_deduplicator": {
|
"01_deduplicator": {
|
||||||
"name": "Buscar duplicados",
|
"name": "Buscar duplicados",
|
||||||
"description": "Coincidencia difusa, normalización, selección de superviviente y revisión interactiva."
|
"description": "Coincidencia difusa, normalización, selección de superviviente y revisión interactiva.",
|
||||||
|
"page_title": "🔍 Buscar duplicados",
|
||||||
|
"page_caption": "Encuentra y elimina filas duplicadas en archivos CSV, texto delimitado y Excel."
|
||||||
},
|
},
|
||||||
"02_text_cleaner": {
|
"02_text_cleaner": {
|
||||||
"name": "Limpiar texto",
|
"name": "Limpiar texto",
|
||||||
"description": "Recorte de espacios, colapso de espacios múltiples, normalización Unicode, manejo de BOM y de finales de línea."
|
"description": "Recorte de espacios, colapso de espacios múltiples, normalización Unicode, manejo de BOM y de finales de línea.",
|
||||||
|
"page_title": "✂️ Limpiar texto",
|
||||||
|
"page_caption": "Recorta espacios, normaliza comillas tipográficas, elimina caracteres invisibles y unifica saltos de línea. Se ejecuta localmente — tus datos nunca salen de este equipo."
|
||||||
},
|
},
|
||||||
"03_format_standardizer": {
|
"03_format_standardizer": {
|
||||||
"name": "Estandarizar formatos",
|
"name": "Estandarizar formatos",
|
||||||
"description": "Estandariza fechas, monedas, nombres, números de teléfono y direcciones."
|
"description": "Estandariza fechas, monedas, nombres, números de teléfono y direcciones.",
|
||||||
|
"page_title": "📐 Estandarizar formatos",
|
||||||
|
"page_caption": "Canoniza fechas, números de teléfono, monedas, nombres, direcciones y booleanos columna por columna. Se ejecuta localmente — tus datos nunca salen de este equipo."
|
||||||
},
|
},
|
||||||
"04_missing_handler": {
|
"04_missing_handler": {
|
||||||
"name": "Corregir valores faltantes",
|
"name": "Corregir valores faltantes",
|
||||||
"description": "Detecta nulos disfrazados, analiza la ausencia de datos y aplica estrategias de imputación."
|
"description": "Detecta nulos disfrazados, analiza la ausencia de datos y aplica estrategias de imputación.",
|
||||||
|
"page_title": "🕳️ Corregir valores faltantes",
|
||||||
|
"page_caption": "Detecta nulos disfrazados, perfila la ausencia de datos y aplica imputación o estrategias de descarte. Se ejecuta localmente — tus datos nunca salen de este equipo."
|
||||||
},
|
},
|
||||||
"05_column_mapper": {
|
"05_column_mapper": {
|
||||||
"name": "Mapear columnas",
|
"name": "Mapear columnas",
|
||||||
"description": "Renombra columnas, aplica un esquema objetivo y fuerza tipos de datos."
|
"description": "Renombra columnas, aplica un esquema objetivo y fuerza tipos de datos.",
|
||||||
|
"page_title": "🗂️ Mapear columnas",
|
||||||
|
"page_caption": "Renombra columnas, aplica un esquema objetivo y fuerza tipos. Se ejecuta localmente — tus datos nunca salen de este equipo."
|
||||||
},
|
},
|
||||||
"06_outlier_detector": {
|
"06_outlier_detector": {
|
||||||
"name": "Detectar valores atípicos",
|
"name": "Detectar valores atípicos",
|
||||||
"description": "Detección por Z-score, IQR y MAD con reglas de dominio y winsorización."
|
"description": "Detección por Z-score, IQR y MAD con reglas de dominio y winsorización.",
|
||||||
|
"page_title": "📊 Detectar valores atípicos",
|
||||||
|
"page_caption": "Detecta y trata valores atípicos en columnas numéricas."
|
||||||
},
|
},
|
||||||
"07_multi_file_merger": {
|
"07_multi_file_merger": {
|
||||||
"name": "Combinar archivos",
|
"name": "Combinar archivos",
|
||||||
"description": "Combina varios archivos CSV/Excel alineando sus esquemas."
|
"description": "Combina varios archivos CSV/Excel alineando sus esquemas.",
|
||||||
|
"page_title": "📎 Combinar archivos",
|
||||||
|
"page_caption": "Combina varios archivos CSV y Excel en un único conjunto de datos."
|
||||||
},
|
},
|
||||||
"08_validator_reporter": {
|
"08_validator_reporter": {
|
||||||
"name": "Verificación de calidad",
|
"name": "Verificación de calidad",
|
||||||
"description": "Valida contra reglas y genera informes de calidad en PDF/Excel."
|
"description": "Valida contra reglas y genera informes de calidad en PDF/Excel.",
|
||||||
|
"page_title": "✅ Verificación de calidad",
|
||||||
|
"page_caption": "Valida datos contra reglas y genera informes de calidad."
|
||||||
},
|
},
|
||||||
"09_pipeline_runner": {
|
"09_pipeline_runner": {
|
||||||
"name": "Flujos automatizados",
|
"name": "Flujos automatizados",
|
||||||
"description": "Encadena herramientas en el orden recomendado y pasa la salida entre pasos."
|
"description": "Encadena herramientas en el orden recomendado y pasa la salida entre pasos.",
|
||||||
|
"page_title": "⚙️ Flujos automatizados",
|
||||||
|
"page_caption": "Encadena pasos de limpieza de DataTools en un flujo repetible. La canalización recomienda un orden; tú mantienes el control."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
|
|||||||
@@ -52,15 +52,15 @@ PAGE_SLUGS = [
|
|||||||
# When a page gains real Spanish translation, flip its 'es' entry to
|
# When a page gains real Spanish translation, flip its 'es' entry to
|
||||||
# the localized substring — the test surface stays the same.
|
# the localized substring — the test surface stays the same.
|
||||||
EXPECTED_SUBSTRINGS: dict[str, dict[str, str]] = {
|
EXPECTED_SUBSTRINGS: dict[str, dict[str, str]] = {
|
||||||
"1_Deduplicator": {"en": "Find Duplicates", "es": "Find Duplicates"},
|
"1_Deduplicator": {"en": "Find Duplicates", "es": "Buscar duplicados"},
|
||||||
"2_Text_Cleaner": {"en": "Clean Text", "es": "Clean Text"},
|
"2_Text_Cleaner": {"en": "Clean Text", "es": "Limpiar texto"},
|
||||||
"3_Format_Standardizer": {"en": "Standardize", "es": "Standardize"},
|
"3_Format_Standardizer": {"en": "Standardize", "es": "Estandarizar"},
|
||||||
"4_Missing_Values": {"en": "Fix Missing", "es": "Fix Missing"},
|
"4_Missing_Values": {"en": "Fix Missing", "es": "Corregir valores"},
|
||||||
"5_Column_Mapper": {"en": "Map Columns", "es": "Map Columns"},
|
"5_Column_Mapper": {"en": "Map Columns", "es": "Mapear columnas"},
|
||||||
"6_Outlier_Detector": {"en": "Unusual", "es": "Unusual"},
|
"6_Outlier_Detector": {"en": "Unusual", "es": "atípicos"},
|
||||||
"7_Multi_File_Merger": {"en": "Combine Files", "es": "Combine Files"},
|
"7_Multi_File_Merger": {"en": "Combine Files", "es": "Combinar archivos"},
|
||||||
"8_Validator_Reporter": {"en": "Quality Check", "es": "Quality Check"},
|
"8_Validator_Reporter": {"en": "Quality Check", "es": "Verificación de calidad"},
|
||||||
"9_Pipeline_Runner": {"en": "Automated", "es": "Automated"},
|
"9_Pipeline_Runner": {"en": "Automated", "es": "Flujos automatizados"},
|
||||||
"99_Close": {"en": "Shutting down", "es": "Cerrando"},
|
"99_Close": {"en": "Shutting down", "es": "Cerrando"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user