chore(ui): rename Upload → Import in user-facing strings
DataTools is local-first — "Upload" reads like "send data somewhere
remote", which contradicts the product positioning. Sweep replaces
the user-visible term throughout the UI:
- ``src/i18n/packs/en.json`` + ``es.json``: all ``upload.*`` strings
(heading, intro, uploader labels, empty state, switch-back, etc.)
and ``gate.default_name``. The ``intro_multi`` "no upload anywhere"
phrasing dropped the verb entirely — now reads "nothing leaves
this computer".
- All 9 tool pages: ``st.file_uploader(label="Upload …")`` →
``"Import …"``; matching ``st.info("Upload a …")`` empty-state
banners; ``help="Upload …"`` strings on disabled uploaders.
- ``9_Pipeline_Runner`` + ``5_Column_Mapper``: radio-option text
``"Upload schema/pipeline JSON"`` → ``"Import …"`` plus the
``.startswith("Upload")`` branch guards that read those values.
- ``_home.py``: "**Uploaded files**" → "**Imported files**".
- ``app_demo.py``: "Uploaded file is …" → "Imported file is …".
Internal identifiers left untouched: function names
(``pickup_or_upload``, ``_StashedUpload``), session-state keys
(``home_upload``, ``home_uploads``, ``home_uploaded_*``,
``merger_file_upload``), audit-log event category (``"upload"``),
Streamlit testid CSS selectors. None of those are visible to the
user.
The file_uploader's dropzone button text is a baked-in React
literal that Streamlit's ``label=`` doesn't reach; rewritten at the
DOM level with a small ``_RENAME_UPLOAD_BUTTON_JS`` snippet shipped
through ``st.iframe`` (same pattern the sticky footer uses to mount
on ``<body>``). A ``MutationObserver`` on the parent document re-
applies the swap when Streamlit remounts the dropzone after file
add/remove or page navigation, throttled via ``requestAnimationFrame``.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -156,7 +156,7 @@ def _home_page() -> None:
|
||||
# interleaving widget-key updates with state changes.
|
||||
if home_uploads:
|
||||
import hashlib
|
||||
st.markdown("**Uploaded files**")
|
||||
st.markdown("**Imported files**")
|
||||
to_remove: str | None = None
|
||||
for name in list(home_uploads.keys()):
|
||||
digest = hashlib.sha1(
|
||||
|
||||
@@ -239,7 +239,7 @@ def _read_uploaded(uploaded_file) -> tuple[pd.DataFrame, list[str]]:
|
||||
size_mb = len(raw) / 1024 / 1024
|
||||
if size_mb > DEMO_FILE_CAP_MB:
|
||||
warnings.append(
|
||||
f"Uploaded file is {size_mb:.1f} MB — demo capped at "
|
||||
f"Imported file is {size_mb:.1f} MB — demo capped at "
|
||||
f"{DEMO_FILE_CAP_MB} MB. The paid product has no size limit."
|
||||
)
|
||||
return sample_df.copy(), warnings
|
||||
|
||||
@@ -347,7 +347,7 @@ body, .stApp {
|
||||
border-radius: var(--dt-r-sm) !important;
|
||||
}
|
||||
/* Hide Streamlit's built-in compact file-chip row once files exist —
|
||||
the home page renders its own canonical "Uploaded files" list with
|
||||
the home page renders its own canonical "Imported files" list with
|
||||
a Remove button beneath the uploader, so the chip row is redundant
|
||||
and visually doubles up on filenames. The dropzone's borderless
|
||||
``+`` button is left in place as the "add more files" affordance.
|
||||
@@ -459,6 +459,56 @@ div[data-testid="stContainer"][data-border="true"] {
|
||||
"""
|
||||
|
||||
|
||||
# Streamlit ships the file_uploader's dropzone button with hard-coded
|
||||
# "Upload" text (it's a text node baked into the React component, not
|
||||
# a Streamlit i18n string we can override from Python). Our product
|
||||
# positioning is local-first, so the word "Upload" is misleading. This
|
||||
# script walks the dropzone buttons after first paint and rewrites the
|
||||
# label to "Import" — and re-runs on Streamlit's component-rerender
|
||||
# DOM mutations so the swap survives navigation and reruns.
|
||||
_RENAME_UPLOAD_BUTTON_JS = """
|
||||
<script>
|
||||
(function () {
|
||||
function swap(doc) {
|
||||
var dropzones = doc.querySelectorAll('[data-testid="stFileUploaderDropzone"]');
|
||||
dropzones.forEach(function (dz) {
|
||||
var btn = dz.querySelector('button');
|
||||
if (!btn) return;
|
||||
// The label is a text node directly inside the outer label span;
|
||||
// walk all text nodes and replace any exact "Upload".
|
||||
var walker = doc.createTreeWalker(btn, NodeFilter.SHOW_TEXT, null, false);
|
||||
var node;
|
||||
while ((node = walker.nextNode())) {
|
||||
if (node.nodeValue && node.nodeValue.trim() === 'Upload') {
|
||||
node.nodeValue = node.nodeValue.replace('Upload', 'Import');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
var doc = window.parent.document;
|
||||
swap(doc);
|
||||
// Streamlit re-mounts dropzone subtrees on file changes / page
|
||||
// switches — observe the parent doc and re-apply the swap when
|
||||
// new ``stFileUploaderDropzone`` nodes appear. Throttled via
|
||||
// requestAnimationFrame so a burst of mutations is one swap.
|
||||
var raf = 0;
|
||||
var obs = new (doc.defaultView || window).MutationObserver(function () {
|
||||
if (raf) return;
|
||||
raf = (doc.defaultView || window).requestAnimationFrame(function () {
|
||||
raf = 0;
|
||||
swap(doc);
|
||||
});
|
||||
});
|
||||
obs.observe(doc.body, { childList: true, subtree: true });
|
||||
} catch (e) {
|
||||
swap(document);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
def hide_streamlit_chrome(*, gate_license: bool = True) -> None:
|
||||
"""Inject CSS to hide Streamlit's default header, menu, and footer.
|
||||
|
||||
@@ -477,6 +527,10 @@ def hide_streamlit_chrome(*, gate_license: bool = True) -> None:
|
||||
"""
|
||||
st.markdown(_HIDE_CHROME_CSS, unsafe_allow_html=True)
|
||||
st.markdown(_DESIGN_TOKENS_CSS, unsafe_allow_html=True)
|
||||
# ``st.markdown`` doesn't execute embedded scripts; ship the
|
||||
# Upload→Import rewriter through an iframe component the same way
|
||||
# the sticky footer mounts on ``<body>``.
|
||||
st.iframe(_RENAME_UPLOAD_BUTTON_JS, height=1)
|
||||
# Stamp a session-start record into the audit log the first time
|
||||
# any page renders. Idempotent — subsequent calls are no-ops.
|
||||
# Wrapped because a broken audit log MUST NOT take the GUI down.
|
||||
|
||||
@@ -69,7 +69,7 @@ st.caption(t("tools.01_deduplicator.page_caption"))
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = pickup_or_upload(
|
||||
label="Upload CSV or Excel file",
|
||||
label="Import CSV or Excel file",
|
||||
key="dedup_file_upload",
|
||||
types=["csv", "tsv", "xlsx", "xls"],
|
||||
help="Supports CSV, TSV, and Excel files. Encoding and delimiters are auto-detected.",
|
||||
@@ -399,7 +399,7 @@ if uploaded is not None:
|
||||
|
||||
else:
|
||||
# No file uploaded — show placeholder
|
||||
st.info("Upload a file to get started.")
|
||||
st.info("Import a file to get started.")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -53,13 +53,13 @@ st.caption(t("tools.02_text_cleaner.page_caption"))
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = pickup_or_upload(
|
||||
label="Upload CSV or Excel file",
|
||||
label="Import CSV or Excel file",
|
||||
key="textclean_file_upload",
|
||||
types=["csv", "tsv", "xlsx", "xls"],
|
||||
)
|
||||
|
||||
if uploaded is None:
|
||||
st.info("Upload a CSV, TSV, or Excel file to begin.")
|
||||
st.info("Import a CSV, TSV, or Excel file to begin.")
|
||||
st.stop()
|
||||
|
||||
|
||||
|
||||
@@ -52,13 +52,13 @@ st.caption(t("tools.03_format_standardizer.page_caption"))
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = pickup_or_upload(
|
||||
label="Upload CSV or Excel file",
|
||||
label="Import CSV or Excel file",
|
||||
key="fmtstd_file_upload",
|
||||
types=["csv", "tsv", "xlsx", "xls"],
|
||||
)
|
||||
|
||||
if uploaded is None:
|
||||
st.info("Upload a CSV, TSV, or Excel file to begin.")
|
||||
st.info("Import a CSV, TSV, or Excel file to begin.")
|
||||
st.stop()
|
||||
|
||||
|
||||
|
||||
@@ -53,13 +53,13 @@ st.caption(t("tools.04_missing_handler.page_caption"))
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = pickup_or_upload(
|
||||
label="Upload CSV or Excel file",
|
||||
label="Import CSV or Excel file",
|
||||
key="missing_file_upload",
|
||||
types=["csv", "tsv", "xlsx", "xls"],
|
||||
)
|
||||
|
||||
if uploaded is None:
|
||||
st.info("Upload a CSV, TSV, or Excel file to begin.")
|
||||
st.info("Import a CSV, TSV, or Excel file to begin.")
|
||||
st.stop()
|
||||
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ st.caption(t("tools.05_column_mapper.page_caption"))
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = pickup_or_upload(
|
||||
label="Upload CSV or Excel file",
|
||||
label="Import CSV or Excel file",
|
||||
key="colmap_file_upload",
|
||||
types=["csv", "tsv", "xlsx", "xls"],
|
||||
)
|
||||
|
||||
if uploaded is None:
|
||||
st.info("Upload a CSV, TSV, or Excel file to begin.")
|
||||
st.info("Import a CSV, TSV, or Excel file to begin.")
|
||||
st.stop()
|
||||
|
||||
|
||||
@@ -122,12 +122,12 @@ with st.expander("Options", expanded=not _has_result):
|
||||
"How would you like to define the target schema?",
|
||||
[
|
||||
"Build interactively (start from current columns)",
|
||||
"Upload schema JSON",
|
||||
"Import schema JSON",
|
||||
"Skip (rename / coerce only — no schema)",
|
||||
],
|
||||
index=0,
|
||||
help=(
|
||||
"An interactive build is fastest for one-off cleanup. Upload a JSON "
|
||||
"An interactive build is fastest for one-off cleanup. Import a JSON "
|
||||
"when you have a fixed contract (a CRM import format, db schema). "
|
||||
"Skip when you only want to rename or coerce specific columns."
|
||||
),
|
||||
@@ -135,7 +135,7 @@ with st.expander("Options", expanded=not _has_result):
|
||||
|
||||
schema: TargetSchema | None = None
|
||||
|
||||
if schema_mode.startswith("Upload"):
|
||||
if schema_mode.startswith("Import"):
|
||||
schema_file = st.file_uploader(
|
||||
"Schema JSON",
|
||||
type=["json"],
|
||||
|
||||
@@ -57,9 +57,9 @@ st.divider()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = st.file_uploader(
|
||||
"Upload CSV or Excel file",
|
||||
"Import CSV or Excel file",
|
||||
type=["csv", "tsv", "xlsx", "xls"],
|
||||
help="Upload a file to preview. Processing is not yet available.",
|
||||
help="Import a file to preview. Processing is not yet available.",
|
||||
key="outlier_file_upload",
|
||||
)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ st.info("This tool is under development.")
|
||||
|
||||
st.markdown("""
|
||||
**Features:**
|
||||
- Upload multiple CSV/Excel files at once
|
||||
- Import multiple CSV/Excel files at once
|
||||
- Automatic schema alignment (matching columns by name)
|
||||
- Append mode: stack files vertically (union)
|
||||
- Join mode: merge files on shared key columns
|
||||
@@ -57,10 +57,10 @@ st.divider()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded_files = st.file_uploader(
|
||||
"Upload CSV or Excel files",
|
||||
"Import CSV or Excel files",
|
||||
type=["csv", "tsv", "xlsx", "xls"],
|
||||
accept_multiple_files=True,
|
||||
help="Upload multiple files to preview. Processing is not yet available.",
|
||||
help="Import multiple files to preview. Processing is not yet available.",
|
||||
key="merger_file_upload",
|
||||
)
|
||||
|
||||
|
||||
@@ -57,9 +57,9 @@ st.divider()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = st.file_uploader(
|
||||
"Upload CSV or Excel file",
|
||||
"Import CSV or Excel file",
|
||||
type=["csv", "tsv", "xlsx", "xls"],
|
||||
help="Upload a file to preview. Processing is not yet available.",
|
||||
help="Import a file to preview. Processing is not yet available.",
|
||||
key="validator_file_upload",
|
||||
)
|
||||
|
||||
|
||||
@@ -55,13 +55,13 @@ st.caption(t("tools.09_pipeline_runner.page_caption"))
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
uploaded = pickup_or_upload(
|
||||
label="Upload CSV or Excel file",
|
||||
label="Import CSV or Excel file",
|
||||
key="pipeline_file_upload",
|
||||
types=["csv", "tsv", "xlsx", "xls"],
|
||||
)
|
||||
|
||||
if uploaded is None:
|
||||
st.info("Upload a CSV, TSV, or Excel file to begin.")
|
||||
st.info("Import a CSV, TSV, or Excel file to begin.")
|
||||
st.stop()
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ with st.expander("Options", expanded=not _has_result):
|
||||
[
|
||||
"Use the recommended default (text-clean → format → missing → dedup)",
|
||||
"Build interactively",
|
||||
"Upload a saved pipeline JSON",
|
||||
"Import a saved pipeline JSON",
|
||||
],
|
||||
index=0,
|
||||
)
|
||||
@@ -143,7 +143,7 @@ with st.expander("Options", expanded=not _has_result):
|
||||
}
|
||||
for s in default.steps
|
||||
])
|
||||
elif mode.startswith("Upload"):
|
||||
elif mode.startswith("Import"):
|
||||
pipeline_file = st.file_uploader(
|
||||
"Pipeline JSON", type=["json"], key="pipeline_upload",
|
||||
)
|
||||
|
||||
@@ -15,23 +15,23 @@
|
||||
"coming_soon": "Coming Soon"
|
||||
},
|
||||
"upload": {
|
||||
"heading": "📤 Upload one or more files to start",
|
||||
"intro": "Optional: scan an uploaded file for data quality issues and see which tools can fix each one. Skip if you already know what you need.",
|
||||
"heading": "📤 Import one or more files to start",
|
||||
"intro": "Optional: scan an imported file for data quality issues and see which tools can fix each one. Skip if you already know what you need.",
|
||||
"limits": "**Up to 1.5 GB.** Formats: CSV, TSV, XLSX, XLS. Delimiters auto-detected: comma, tab, semicolon, pipe. Encodings auto-detected: UTF-8 (with/without BOM), UTF-16, cp1252, Latin-1/9, cp1250, ISO-8859-2, cp1251, KOI8-R, Mac Roman, Shift_JIS, GB18030, Big5, EUC-KR — and override on the Review page.",
|
||||
"uploader_label": "Upload CSV or Excel",
|
||||
"uploader_label": "Import CSV or Excel",
|
||||
"uploader_help": "Up to 1.5 GB. Comma / tab / semicolon / pipe delimiters all auto-detected. Encoding auto-detected with override on the Review page if needed.",
|
||||
"run_button": "Run analysis",
|
||||
"skip_button": "Skip",
|
||||
"scanning": "Scanning…",
|
||||
"skipped_notice": "Analysis skipped. Open any tool below to start working.",
|
||||
"using_session_file": "Using **{name}** from the upload screen.",
|
||||
"using_session_file": "Using **{name}** from the import screen.",
|
||||
"use_different_file": "Use a different file",
|
||||
"switch_back": "Switch back to upload-screen file",
|
||||
"switch_back": "Switch back to import-screen file",
|
||||
"pickup_caption": "Up to 1.5 GB. Delimiters auto-detected: comma, tab, semicolon, pipe. Encoding auto-detected (UTF-8 / UTF-16 / cp1252 / Latin-1 family / cp1250 / cp1251 / KOI8-R / Mac Roman / Shift_JIS / GB18030 / Big5 / EUC-KR), with override on the Review page.",
|
||||
"intro_multi": "Drop files below. Each one is analyzed locally — no upload anywhere.",
|
||||
"uploader_label_multi": "Upload CSV, TSV, or Excel files",
|
||||
"intro_multi": "Drop files below. Each one is analyzed locally — nothing leaves this computer.",
|
||||
"uploader_label_multi": "Import CSV, TSV, or Excel files",
|
||||
"clear_results": "Clear results",
|
||||
"empty_state": "Upload one or more files to begin. Your data never leaves this computer."
|
||||
"empty_state": "Import one or more files to begin. Your data never leaves this computer."
|
||||
},
|
||||
"findings": {
|
||||
"header": "Detected issues",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"gate": {
|
||||
"warning": "**{name}** must pass the CSV-normalization gate before you can use this tool. Open the Review page to apply the fixes our analyzer recommends.",
|
||||
"default_name": "the uploaded file",
|
||||
"default_name": "the imported file",
|
||||
"open_review": "Go to Review & Normalize"
|
||||
},
|
||||
"quit": {
|
||||
|
||||
@@ -15,23 +15,23 @@
|
||||
"coming_soon": "Próximamente"
|
||||
},
|
||||
"upload": {
|
||||
"heading": "📤 Sube uno o más archivos para empezar",
|
||||
"heading": "📤 Importa uno o más archivos para empezar",
|
||||
"intro": "Opcional: analiza un archivo para detectar problemas de calidad de datos y ver qué herramientas pueden corregir cada uno. Sáltalo si ya sabes lo que necesitas.",
|
||||
"limits": "**Hasta 1,5 GB.** Formatos: CSV, TSV, XLSX, XLS. Delimitadores detectados automáticamente: coma, tabulador, punto y coma, barra vertical. Codificaciones detectadas automáticamente: UTF-8 (con/sin BOM), UTF-16, cp1252, Latin-1/9, cp1250, ISO-8859-2, cp1251, KOI8-R, Mac Roman, Shift_JIS, GB18030, Big5, EUC-KR — y se pueden sustituir desde la página Revisar.",
|
||||
"uploader_label": "Sube un archivo CSV o Excel",
|
||||
"uploader_label": "Importa un archivo CSV o Excel",
|
||||
"uploader_help": "Hasta 1,5 GB. Delimitadores coma / tabulador / punto y coma / barra vertical detectados automáticamente. Codificación detectada automáticamente, con opción de sustituirla en la página Revisar.",
|
||||
"run_button": "Ejecutar análisis",
|
||||
"skip_button": "Omitir",
|
||||
"scanning": "Analizando…",
|
||||
"skipped_notice": "Análisis omitido. Abre cualquier herramienta de abajo para empezar a trabajar.",
|
||||
"using_session_file": "Usando **{name}** de la pantalla de carga.",
|
||||
"using_session_file": "Usando **{name}** de la pantalla de importación.",
|
||||
"use_different_file": "Usar otro archivo",
|
||||
"switch_back": "Volver al archivo de la pantalla de carga",
|
||||
"switch_back": "Volver al archivo de la pantalla de importación",
|
||||
"pickup_caption": "Hasta 1,5 GB. Delimitadores detectados automáticamente: coma, tabulador, punto y coma, barra vertical. Codificación detectada automáticamente (UTF-8 / UTF-16 / cp1252 / familia Latin-1 / cp1250 / cp1251 / KOI8-R / Mac Roman / Shift_JIS / GB18030 / Big5 / EUC-KR), con opción de sustituirla en la página Revisar.",
|
||||
"intro_multi": "Suelta archivos abajo. Cada uno se analiza localmente — no se sube a ningún lado.",
|
||||
"uploader_label_multi": "Sube archivos CSV, TSV o Excel",
|
||||
"intro_multi": "Suelta archivos abajo. Cada uno se analiza localmente — nada sale de este equipo.",
|
||||
"uploader_label_multi": "Importa archivos CSV, TSV o Excel",
|
||||
"clear_results": "Borrar resultados",
|
||||
"empty_state": "Sube uno o más archivos para empezar. Tus datos nunca salen de este equipo."
|
||||
"empty_state": "Importa uno o más archivos para empezar. Tus datos nunca salen de este equipo."
|
||||
},
|
||||
"findings": {
|
||||
"header": "Problemas detectados",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"gate": {
|
||||
"warning": "**{name}** debe pasar la verificación de normalización CSV antes de poder usar esta herramienta. Abre la página Revisar para aplicar las correcciones recomendadas por el analizador.",
|
||||
"default_name": "el archivo cargado",
|
||||
"default_name": "el archivo importado",
|
||||
"open_review": "Ir a Revisar y Normalizar"
|
||||
},
|
||||
"quit": {
|
||||
|
||||
Reference in New Issue
Block a user