diff --git a/.changeset/9327-import-history-time-locale.md b/.changeset/9327-import-history-time-locale.md new file mode 100644 index 0000000000..fd18ba0906 --- /dev/null +++ b/.changeset/9327-import-history-time-locale.md @@ -0,0 +1,27 @@ +--- +"@object-ui/plugin-grid": patch +--- + +The import-job history table formats its timestamps in the tenant's locale, not the machine's + +`formatImportJobTime`, which draws the Time column of the grid's import-job history +panel, ended in a bare `d.toLocaleString()`. An omitted tag does not mean "the user's +locale" — it means the MACHINE's, which is neither of this renderer's two locale +channels, and on a date that moves the FIELD ORDER rather than a separator: the same +instant reads as `04/03/2026` under `en-GB` and `3/4/2026` under `en-US`, both legal and +mutually ambiguous for the first twelve days of every month. An import-history row is +exactly where a reader works out which run was which, and there is no unit marker to +catch the misreading. Measured before the fix: a German session rendered the status badge +as `Erfolgreich` and the timestamp beside it in the machine's American order. + +`ImportHistoryPanel` now reads `useDisplayLocale()` at component level — the same channel +every other date, number and currency renderer in this repo goes through — and hands the +resolved tag to the helper. The helper stays a module-level pure function rather than +being inlined into the component, and its `locale` parameter is a REQUIRED, non-optional +`string`: under an optional spelling a caller could drop the argument, still type-check, +and render a plausible date instead of an error. There is no renderer-side `?? 'en'` +backstop; `useDisplayLocale` already owns the last resort and always returns a concrete +tag. + +No published symbol moves — both the helper and the panel are module-private. Behaviour is +byte-identical wherever the resolved tag already agreed with the machine. diff --git a/packages/plugin-grid/src/ImportWizard.tsx b/packages/plugin-grid/src/ImportWizard.tsx index 7c810c531d..e1f7859db3 100644 --- a/packages/plugin-grid/src/ImportWizard.tsx +++ b/packages/plugin-grid/src/ImportWizard.tsx @@ -13,6 +13,7 @@ import { import { Upload, FileSpreadsheet, CheckCircle2, AlertCircle, X, ArrowRight, ArrowLeft, Save, Trash2, ClipboardPaste, Download, Undo2 } from 'lucide-react'; import { useObjectTranslation } from '@object-ui/react'; import { sanitizeFileNameBase } from '@object-ui/core'; +import { useDisplayLocale } from '@object-ui/i18n'; import { BOOLEAN_IMPORT_TOKENS, REFERENCE_IMPORT_TYPES } from './importCoercionContract'; import type { DataSource, @@ -1428,12 +1429,27 @@ const IMPORT_JOB_STATUS_VARIANT: Record) => string; }> = ({ objectName, dataSource, t }) => { + // The one date/number locale resolver: tenant regional default -> active UI + // language -> 'en'. Read unconditionally at component level, then handed to + // `formatImportJobTime` as an argument — the helper stays a module-level pure + // function rather than being inlined here just to reach the hook. + const displayLocale = useDisplayLocale(); const [jobs, setJobs] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -1543,7 +1564,7 @@ const ImportHistoryPanel: React.FC<{ {job.skipped > 0 && · {t('grid.import.skippedCount', { count: job.skipped })}} {job.errors > 0 && · {t('grid.import.errorCount', { count: job.errors })}} - {formatImportJobTime(job.completedAt ?? job.createdAt)} + {formatImportJobTime(job.completedAt ?? job.createdAt, displayLocale)} {isImportJobActive(job.status) && typeof ds?.cancelImportJob === 'function' && (