Skip to content

feat(i18n): enable UI translations with a per-user language setting [1/3]#15091

Draft
devGregA wants to merge 5 commits into
DefectDojo:devfrom
devGregA:devgrega/os-i18n
Draft

feat(i18n): enable UI translations with a per-user language setting [1/3]#15091
devGregA wants to merge 5 commits into
DefectDojo:devfrom
devGregA:devgrega/os-i18n

Conversation

@devGregA

Copy link
Copy Markdown
Contributor

DefectDojo's templates and models are already full of translation markers
({% trans %}, gettext), and there are even committed pt_BR and ru catalogs,
but the runtime was never wired up, so nothing actually translated. This turns
it on and adds a per-user language preference.

What's here

  • Enable Django i18n: add LocaleMiddleware, LANGUAGES, and LOCALE_PATHS.
  • Per-user language: a language field on UserContactInfo (migration 0271)
    and a selector on the profile page.
  • Resolution is cookie-based. The preference lives in the DB so it follows a user
    across devices, and it's cached in the standard django_language cookie that
    LocaleMiddleware reads on each request. We only touch the DB to seed the
    cookie when it's missing (roughly once per browser session) and to refresh it
    when the user changes their language, so the steady state has no extra
    per-request query. API requests are skipped and keep honoring Accept-Language.
  • Compile the .mo catalogs into the image at build time (debian and alpine);
    the .mo files stay gitignored.

Languages

English, Portuguese (Brazil), and Russian are offered for now. The pt_BR and ru
catalogs already existed but were never compiled or shipped, so I reviewed them
for broken format placeholders, injected markup, and the wording on
destructive/security actions and found nothing off (and fixed 3 malformed pt_BR
entries that were preventing the catalog from compiling at all). Spanish,
Japanese, German, and French get added once their translations are done.

Scope / non-goals

  • Stored database values and serialized API responses stay in English regardless
    of the selected language; only displayed text changes. Localizing API responses
    and the OpenAPI schema is deliberately left to a follow-up so existing
    integrations aren't affected.
  • This is the first of a few PRs. The next one wraps form labels, flash messages,
    and the remaining hardcoded template strings that aren't marked yet (which is
    why some form fields still render in English here).

Based on #15089 (OS message opt-out). If that hasn't merged yet, its commits will
show up in this diff.

Testing

Verified on a local instance: switching a user's language renders the UI in
Portuguese/Russian and falls back to English. compilemessages succeeds for all
catalogs and ruff passes.

devGregA and others added 4 commits June 25, 2026 20:21
…r the OS promo banner

DD_OS_MESSAGE_ENABLED (bool, default True) is a global admin opt-out: when False, get_os_banner() returns None before any network call, so no request is made to the GCS bucket. Default behavior is unchanged.

Authenticated users can also dismiss the banner via a close (x) button. The dismissal is stored as a hash of the current message on UserContactInfo (mirroring the ui_use_tailwind preference), so the banner reappears only when the promo text changes. The button posts to a new dismiss_os_message endpoint (form-based CSRF) and hides the banner instantly via JS, degrading to a normal POST when JS is disabled.

Includes migration 0270, docs and template-env updates, and unit tests (36 passing in unittests/test_os_message.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…utton

Moves the dismiss (x) to lead the banner, before the headline, so it is clearly separated from the expand caret and avoids misclicks. Replaces the reused Bootstrap '.close' class (which collided with auto-generated v3 UI styles) with a dedicated 'os-message-dismiss' class styled as a small bordered button.

Lays out the OS banner as a centered flex row scoped to [data-source="os"], so the styling applies only to this banner; other banners and the messages notifications are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…URE_LOCATIONS

TestDismissOsMessageView loaded dojo_testdata.json directly, which raises NotImplementedError under V3_FEATURE_LOCATIONS=True (the fixture contains deprecated Endpoint records). Adding the @versioned_fixtures decorator swaps it to dojo_testdata_locations.json under V3, matching the convention used by the rest of the suite. Test-only change; verified passing locally under both V3=True and V3=False.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enables Django i18n in the OSS UI and lights up the existing (audited) pt_BR/ru catalogs. settings: add LocaleMiddleware, LANGUAGES (en, pt-br, ru), LOCALE_PATHS. Per-user language: UserContactInfo.language (migration 0271) + a profile selector. The DB is the cross-device source of truth; the django_language cookie caches it so LocaleMiddleware resolves per request with no DB access. The cookie is seeded from the DB only when absent (~once per browser session) and refreshed on change; /api/ requests are skipped (they localize via Accept-Language).

Build: compile .mo into the image via msgfmt (the .mo are gitignored), debian + alpine. Also fixes 3 malformed entries in the pre-existing pt_BR catalog so it compiles.

Only en/pt-br/ru are offered (audited); es/ja/de/fr are added once translated. Stored DB values and serialized API responses stay English regardless of language.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added docker New Migration Adding a new migration file. Take care when merging. settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR docs unittests ui localization labels Jun 26, 2026
@devGregA devGregA changed the title feat(i18n): enable UI translations with a per-user language setting feat(i18n): enable UI translations with a per-user language setting [1/3] Jun 26, 2026
…config

Adds unittests/test_i18n.py: set_language_cookie sets/clears the cookie; LanguagePreferenceMiddleware seeds+activates from the DB only when the cookie is absent, and is a no-op for present cookies, /api/ paths, anonymous users, and users with no stored preference; and config checks (LocaleMiddleware enabled, preference middleware after auth, offered languages, LOCALE_PATHS). 11 tests, SimpleTestCase (no DB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts-detected docker docs localization New Migration Adding a new migration file. Take care when merging. settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR ui unittests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant