Say which language and time zone “the default” is, with its flag and name, in Settings and Server settings #698
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#698
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?
Where a person, or an administrator, has chosen nothing, the language and the time zone menus say “Use the instance default” and leave them to guess what that is. The maintainer's list asks for the answer itself, in Settings → Language and time and in Server settings → Defaults: for the language, "Flag + Language (Region) + greyed BCP 47 code + ‘ - Default’"; for the time zone, "Timezone + ‘ - Default’".
What exists today
src/postulo/accounts/forms.py:219(first entry oflanguage_choices()), for the time zone at:289(first entry oftime_zone_choices()). Neither function is told what the default is. The string is in all 68 catalogues (src/postulo/locale/*/LC_MESSAGES/django.po).<select>(templates/settings/locale.html:28-101).LocaleForm.language_groups()builds the rows and writes the first one by hand fromlanguage_choices()[0]: code blank, name “Use the instance default”, no flag, no state (accounts/forms.py:1037-1053);language_now()returns the selected row for the closed summary (:1087-1096). A row's name sits in a<span lang="…">and the words about it, in the interface language, outside it (partials/translation_state.html); the blank row has nolang, andtests/test_translations.py:411-416asserts that (lang=""must not appear). The time zone is a plain field with the blank option first (settings/locale.html,c-field :field="form.time_zone"; optionsname.replace("_", " "),accounts/forms.py:289-291).DefaultsForm(core/server_forms.py:259-282). There the blank choice means something else: nothing stored, so the built-in fallback, which issettings.LANGUAGE_CODE(the source language,config/settings/base.py:458) andsettings.TIME_ZONE(POSTULO_TIME_ZONE,Europe/Parisunless set,:468). Labelled “Use the instance default” on the page that sets the instance default. The language is a native<select>with the chosen flag drawn beside it by script (templates/server/defaults.html:30-35,data-flag-select, #208). Under the card, “In force now: {language}, {zone}.” prints raw values: a language code such asen-GBand a zone name (defaults.html:48-52;DefaultsView.get_context_dataalready computeseffective_languageandeffective_time_zone,core/server_views.py:573-579;site.default_language()andsite.default_time_zone()arecore/site.py:207-215).UserPreferencesMiddlewarecallspreferences.apply, which takes the person's zone, else the instance default (src/postulo/core/preferences.py:34-42,instance_zone()at:69; the call is atcore/middleware.py:206). The language does not. A profile with no language is left to whateverLocaleMiddlewarenegotiated from the browser'sAccept-Language; the instance default is never consulted (core/preferences.py:50-56, where the rule moved with #393; it was in the middleware before).apply's own docstring says it: without a profile “the language is left as it stands, which is whateverLocaleMiddlewaretook from the request”. That is #398, open, and it is also the maintainer's own report in this list ("Defined displayed language should be respected no matter the device. Currently it follows the demanded browser language, different from desktop and mobile browser"). So a label that says “English (United Kingdom) — Default” would be wrong on the page it is written on until #398 is fixed.core/languages.pyholds each language's name in itself,NATIVE_NAMES(:139-212), 69 tags. Exactly five carry anything beyond the language:en-GB“English (United Kingdom)”,fr-FR“français (France)”,pt-PT“português (Portugal)”,pt-BR“português (Brasil)” (a region each, written by hand) andsr-Cyrl“српски” (a script, not in the name). The other 64 are the language alone (“Deutsch”, “español”).languages.flag_country(code)gives the country whose flag stands for a language; 13 languages have none by design (ar ee ff ha ig ln om ss st sw ti tn yo,core/languages.py:60-66), and{% language_flag %}draws nothing for them (core/templatetags/postulo.py:94-103).documents/forms.py:32has alanguage_choices()of its own (blank means “Follow your profile”). Only Settings and Server settings readaccounts.forms.language_choices, and so doesLocaleForm.language_groups().Checked against
mainatf09b6010b.Proposal
language_choices(default=…)andtime_zone_choices(default=…)take the value the blank choice stands for: in Settings,site.default_language()andsite.default_time_zone(); in Server settings → Defaults, the built-insettings.LANGUAGE_CODEandsettings.TIME_ZONE. The stored value of the blank choice stays the empty string, so it still means “follow the default”, not “pin today's default”.flag_country(code)(decorative, as every flag here); the language's name in its own language in thelangspan; the BCP 47 tag, in the secondary text colour, in a<bdi dir="ltr">so a right-to-left page does not reorderen-GB; then the word “Default” with its separator, outside thelangspan and in the interface language, as the translation state is. The row has alanglike every other row, so the assertion attests/test_translations.py:411-416becomes “the default row's name carries its own language and no text outside it does”.<option>holds text only, so there the flag stays where it is (beside the closed select, drawn by script) and the code and the word follow the name in plain text; the grey code appears wherever the control can draw rows, which is the picker on Settings now and every select once #301 lands. “In force now” names the language the same way instead of printing a code.Decisions
NATIVE_NAMESalready gives the region where there is one. For the other 64 there is no region to show. Options: show the language alone; or add a region taken from the flag's country (“Deutsch (Deutschland)” forde); or take regional names from CLDR (Babel, not a dependency: see #689). Recommended: the language alone, and a region exactly where the tag has one. A region the tag does not carry is a claim the code never made (deis notde-DE), and 13 languages have no country at all. If every name must read “Language (Region)”, that is part of #689, not this issue.base.html:112). Recommended: a spaced em dash, in one message with a placeholder so a language can change it.msgid "Default"for the button-size menu (core/design.py:90); reusing it would tie two meanings to one translation. Recommended: a new message with a context saying it names the value used when nothing was chosen.aria-hiddenon the code, since the name already says what it is; the flag stays decorative.language_choices()skips both). Recommended: the row still names it, because it is what the page is in once #398 is fixed; it is a statement of fact about the instance, not an offer.Builds on
#398 (the page ignores the instance default for the language), #208 (a flag beside each language on Defaults), #119 (the picker as rows, the state words outside the
langspan), #64 (each language named in itself), #337 (BCP 47 tags), #312 (the translation bar on Defaults), #322 (a region survives in a name), #301 (Basecoat's select, where each option can be a row). #689 is the general source of names with a region. #492 and #426 are open bugs on the same two forms and do not overlap.Done when
lang, the grey tag and “Default”; with the instance default set todeit is “Deutsch de — Default” and no flag is a wrong one; for a language with no flag the row closes up. New cases intests/test_language_picker.pyandtests/test_translations.py(thelangassertion above rewritten, not removed);tests/e2e/test_language_picker.py:100still finds the first row's value empty.Europe/Parisand an administrator-stored zone both covered, and the environment-pinned case (site.default_time_zone()returnssettings.TIME_ZONEwhen the environment overrides it,core/site.py:207-210).tests/test_settings.py.tests/test_server_settings.py.tests/test_language_of_the_document.py,tests/test_career_languages.pystill pass).tests/test_contrast.py) and the axe run over both pages (tests/e2e/test_accessibility.py) is clean; a right-to-left page keeps the tag whole (tests/e2e/test_direction.py).scripts/messages.py extract; Weblate (#349) shows “Use the instance default” gone and the new message waiting. The wiki's Configuration page names the labels in the same commit.From the list of improvements of 2026-10-02.