Spreadsheet import: status keys in the preview, English skip reasons, counts without plural forms, a tab title fixed at first load #395
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#395
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the spreadsheet import view, its templates andcsv_import.perform, and checking the templates for counts in sentences.Checked again on 2026-10-01 against
mainat0a5cad587: still present.core/csv_import.pyshifted by a few lines when the currency list moved tocore/currencies.py(#248). The code is now atcore/csv_import.py:760(map_status) and:789(ParsedRow) for section 1;:944,:948and:964-967(the skip reasons inperform) and:889-891(the translated problems) for section 2; and:441(Sheet.row_count) for section 3. The templates,core/views_import.pyandtests/test_csv_import.py:231have not moved.The spreadsheet import's mapping and result pages carry four kinds of text that do not follow the reader's language. The preview's Status column prints Postulo's internal key rather than the status's name; the "Why rows were skipped" list is built from English f-strings; two sentences put numbers before fixed plural nouns ("1 rows"); and the section title in the browser tab is translated once, when the module is first imported. Every user-facing string is meant to be translatable, and the preview is the step where a person checks that their sheet was read correctly.
1. The preview's Status column shows the internal key
templates/core/import_csv_map.html:92prints{{ row.status }}, the valuemap_status(core/csv_import.py:765) returns:"interviewing","ghosted","draft", notStatus(row.status).label.ParsedRow(core/csv_import.py:794) astatus_labelproperty returningStatus(self.status).label, or pass the labels to the template, and print that atimport_csv_map.html:92.2. The skip reasons are English
core/csv_import.py:949,:953and:969-972inperformappendf"row {row.number}: ...","already recorded (same address)"and"already recorded ({row.role} at {company.name}, {row.applied_at})"toreport.skipped, whichtemplates/core/import_csv_done.html:25lists as they are, under the translated heading "Why rows were skipped". The problems joined into the first one (no company,no role,core/csv_import.py:894-896) are translated; the words around them are not.gettextand named placeholders, for example_("Row %(number)s: already recorded (same address)"),_("Row %(number)s: already recorded (%(role)s at %(company)s, %(date)s)")and_("Row %(number)s: %(problems)s"), formatting the date withdate_format(row.applied_at, "DATE_FORMAT").3. Row counts are stated without plural forms
templates/core/import_csv_map.html:10-12(<strong>{{ name }}</strong>: {{ rows }} rows. The guesses below …) andtemplates/core/import_csv_map.html:109-111(Across the whole file: {{ applications }} applications, {{ listings }} listings, {{ skipped }} rows skipped for want of a company or a role. …). Both areblocktranslateblocks with nocountand no{% plural %}branch. The numbers come fromSheet.row_count(core/csv_import.py:437) andcore/views_import.py:80-82.tests/test_csv_import.py:231pins the sentence as it is today ("3 applications, 1 listings, 1 rows skipped").{% blocktranslate trimmed count rows=sheet.row_count with name=sheet.filename %}…{{ rows }} row.…{% plural %}…{{ rows }} rows.…{% endblocktranslate %}. For the summary, show the three counts as label and number pairs, astemplates/core/import_csv_done.html:14-20already does ("Applications", "Listings", "Rows skipped"), and keep the explanatory sentence separate and free of counts. Update the assertion attests/test_csv_import.py:231with it.4. The tab title is translated when the module is imported
core/views_import.py:9imports the eagergettext as _, andcore/views_import.py:14evaluatesSECTION = {"section_title": _("Your data")}at module level.templates/settings/base.html:4puts it in the page<title>("Settings > Your data").views_importwas first loaded, in the tab title for everyone served by that process, althoughlocale/fr_FR/LC_MESSAGES/django.potranslates it as "Vos données".gettext_lazyforSECTION, asaccounts/settings_views.py:15andcore/server_views.py:28do forsection_title, keeping the eagergettextfor the messages inside the view; or build the dictionary inside the view.Done when
tests/test_csv_import.pyrenders the mapping page undertranslation.override("fr-fr")(or with a stubbed catalogue) and finds the translated status label, not the key, in the preview row.tests/test_csv_import.pychecks that a duplicate row's skip reason passes through the catalogue:performunder an overridden language does not return the English "already recorded", and the result page underfr-frshows no English skip reason.tests/test_csv_import.pyuploads a file with one data row and finds "1 row." (not "1 rows") on the mapping page, and finds the summary's three numbers each beside its own label.tests/test_csv_import.pychecks thatviews_import.SECTION["section_title"]is a lazy translation (aPromise), not astr.