fix(i18n): localise attachment size displays
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# Attachment size localisation
|
||||
|
||||
3 November 2026. Isolated `codex/i18n-attachment-sizes` from published `ca519cacbec6ee8e385927ad31356dacc18b2f2d`. Ordinary localisation work is authorised independently of the pending A3 increment gate. This branch does not replace the newer A3 application tree.
|
||||
|
||||
`Attachments` and the frozen submitted-package attachment list displayed decimal points through `toFixed`, even with Norwegian UI text. They now use `Intl.NumberFormat` with the existing UI language. The attachment table keeps its existing 1024-based KB/MB threshold, zero KB decimals and one MB decimal. Frozen package labels keep one KB decimal. No stored file size, filename, snapshot, route, API, revision, download or generation language changes.
|
||||
|
||||
Verification:
|
||||
|
||||
- Red regression: two Norwegian-format cases failed, one English case passed; 58 cases were filtered, not executed. Native `attachment-size-red.json`.
|
||||
- Full affected suites: **61 passed**, zero failed/skipped, two suites; `attachment-size-tests.json`. Includes EN/NB MB labels and changing the UI locale on a retained frozen package without a write.
|
||||
- Typecheck, scoped ESLint for the four affected files, static-export build and `git diff --check`: PASSED.
|
||||
- AST-only graph refresh PASSED:18,702 nodes/46,195 edges/1,119 communities. Optional SQL parser is absent and143 files yielded no symbols; these are graph coverage limits. Generated graph stays local.
|
||||
- Independent static review found no actionable issues; verification reruns were not added to the61 cases.
|
||||
|
||||
Rollback reverts these four source/test changes; no database rollback is required. The published branch remains separate until its narrow delta is reconciled against the newer A3 baseline. Existing physical PDF metrics and visual acceptance are unaffected; no renderer source changed.
|
||||
|
||||
NOT RUN: new browser/device or physical PDF checks, complete European localisation, native-language review, backend/provider/live-model or release qualification. Existing tests and code remain responsible for file permissions and downloads; this display-only change adds no new access path. The 188 historical dynamic-call reviews remain accounted for and are not expanded by these three numeric-format regressions.
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"base": "ca519cacbec6ee8e385927ad31356dacc18b2f2d",
|
||||
"policy": "LF-normalised text SHA256",
|
||||
"files": {
|
||||
"job-tracker-ui/src/components/Attachments.tsx": "02081be8282cb3d1a525b336bededb8aabe561cc3b73e1b98e924f9ef945add1",
|
||||
"job-tracker-ui/src/components/ApplicationAssets.tsx": "effcee6cfa0a43ebe0d73a0da625410dc98a6580fddda225b843e5e00d4e45f6",
|
||||
"job-tracker-ui/src/attachments.test.tsx": "89bb3e277ae7be652073eec6d3b6188bcbeaea8b29b07478ff5b21654be8e7c1",
|
||||
"job-tracker-ui/src/application-assets.test.tsx": "980a346cb4452ac4b7e6b689a963e5a23fc6d77410e47e66a422f9cd9c704e7b",
|
||||
"docs/i18n/ATTACHMENT_SIZE_FORMATTING.md": "4576ccfc3de3b72cd3e5591c40f697b827d724b8f4fe588b54567dbe75252332",
|
||||
"docs/i18n/attachment-size-red.json": "872882cb7d2937bf1df7f7a89810158c5de831381f56c6ae8bb0263d26b84d8e",
|
||||
"docs/i18n/attachment-size-tests.json": "8b8188d432b7788f951cf2c67f790c6cd4d24553ed9accbeb702645b3d4b2c6a"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -104,6 +104,18 @@ function changeLanguage(language: UiLanguage) {
|
||||
act(() => { window.dispatchEvent(new StorageEvent("storage", { key: "uiLanguage", newValue: language })); });
|
||||
}
|
||||
|
||||
test('frozen attachment size follows UI language without changing stored package data', async () => {
|
||||
const attachment = { id: 81, fileName: 'recorded.pdf', fileSize: 1536, fileType: 'application/pdf' };
|
||||
const summary = { id: 91, version: 1, cvVariantId: 3, cvVariantName: 'Original CV', cvVariantVersion: 2, cvThemeId: 'nordic', hasCv: true, hasCoverLetter: true, createdAtUtc: '2026-07-19T10:00:00Z', attachments: [attachment] };
|
||||
routeGet({ submittedPackages: [summary] });
|
||||
render(<SubmittedPackagesSection jobId={7} />);
|
||||
expect(await screen.findByRole('button', { name: /recorded.pdf.*1.5 KB/ })).toBeInTheDocument();
|
||||
changeLanguage('nb');
|
||||
expect(screen.getByRole('button', { name: /recorded.pdf.*1,5 KB/ })).toBeInTheDocument();
|
||||
expect(summary.attachments[0]).toEqual(attachment);
|
||||
expect(mockedApi.post).not.toHaveBeenCalled(); expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
let reject!: (reason: unknown) => void;
|
||||
|
||||
@@ -22,6 +22,15 @@ const mockedApi = api as jest.Mocked<typeof api>;
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
test.each([['en', '1.5 MB'], ['nb', '1,5 MB']])('attachment size follows the UI locale (%s)', async (language, size) => {
|
||||
const previous = localStorage.getItem('uiLanguage');
|
||||
localStorage.setItem('uiLanguage', language);
|
||||
mockedApi.get.mockResolvedValueOnce({ data: [{ id: 8, fileName: 'recorded.pdf', fileType: 'application/pdf', fileSize: 1572864, uploadDate: '2026-11-03T10:00:00Z' }] } as any);
|
||||
const view = render(<ToastProvider><I18nProvider><ConfirmProvider><PromptProvider><Attachments jobId={42} /></PromptProvider></ConfirmProvider></I18nProvider></ToastProvider>);
|
||||
try { expect(await screen.findByText(size)).toBeInTheDocument(); expect(mockedApi.post).not.toHaveBeenCalled(); }
|
||||
finally { view.unmount(); if (previous === null) localStorage.removeItem('uiLanguage'); else localStorage.setItem('uiLanguage', previous); }
|
||||
});
|
||||
|
||||
test.each([
|
||||
['en', 'Preview: resume.pdf', 'Preview: resume.pdf'],
|
||||
['nb', 'Forhåndsvis: resume.pdf', 'Forhåndsvisning: resume.pdf'],
|
||||
|
||||
@@ -357,7 +357,7 @@ export function SubmittedPackagesSection({ jobId, allowCapture = true }: { jobId
|
||||
</Button>
|
||||
{item.attachments.map((attachment) => (
|
||||
<Button key={attachment.id} size="small" variant="text" disabled={pending} startIcon={<DownloadOutlinedIcon />} onClick={() => void download(item, attachment)} sx={{ alignSelf: "flex-start", maxWidth: "100%", overflowWrap: "anywhere", textAlign: "left" }}>
|
||||
{attachment.fileName} · {(attachment.fileSize / 1024).toFixed(1)} KB
|
||||
{attachment.fileName} · {new Intl.NumberFormat(language === "nb" ? "nb-NO" : "en", { minimumFractionDigits: 1, maximumFractionDigits: 1 }).format(attachment.fileSize / 1024)} KB
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
@@ -47,11 +47,12 @@ interface AttachmentItem {
|
||||
useForAi: boolean;
|
||||
}
|
||||
|
||||
function fmtSize(n: number) {
|
||||
function fmtSize(n: number, language: string) {
|
||||
if (!n) return "";
|
||||
const kb = n / 1024;
|
||||
if (kb < 1024) return `${kb.toFixed(0)} KB`;
|
||||
return `${(kb / 1024).toFixed(1)} MB`;
|
||||
const digits = kb < 1024 ? 0 : 1;
|
||||
const value = new Intl.NumberFormat(language === "nb" ? "nb-NO" : "en", { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(kb < 1024 ? kb : kb / 1024);
|
||||
return `${value} ${kb < 1024 ? "KB" : "MB"}`;
|
||||
}
|
||||
|
||||
function isImageType(t: string) {
|
||||
@@ -303,7 +304,7 @@ export default function Attachments({ jobId }: { jobId: number }) {
|
||||
</TableCell>
|
||||
<TableCell>{kind}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{a.fileType ? a.fileType.replace("application/", "") : ""}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{a.fileSize ? fmtSize(a.fileSize) : ""}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{a.fileSize ? fmtSize(a.fileSize, language) : ""}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{a.uploadDate ? new Date(a.uploadDate).toLocaleString(language === "nb" ? "nb-NO" : "en") : ""}</TableCell>
|
||||
<TableCell>
|
||||
<FormControl size="small" fullWidth>
|
||||
|
||||
Reference in New Issue
Block a user