Skip to content

feat(locale): add language switching and French translation#37

Merged
PascalRepond merged 1 commit intomainfrom
rep-dev
Jan 4, 2026
Merged

feat(locale): add language switching and French translation#37
PascalRepond merged 1 commit intomainfrom
rep-dev

Conversation

@PascalRepond
Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Adds Django i18n: LocaleMiddleware, LANGUAGES and LOCALE_PATHS; French translation catalog; session-backed language selection view/URL, template language selector, tests, and poe tasks for makemessages/compilemessages.

Changes

Cohort / File(s) Summary
Build & i18n config
pyproject.toml, src/config/settings.py
Added poe tasks makemessages/compilemessages; inserted django.middleware.locale.LocaleMiddleware; added LANGUAGES (en, fr) and LOCALE_PATHS.
Language selection endpoints
src/accounts/views.py, src/accounts/urls.py
New set_language POST-only, login_required view that validates/activates language and stores django_language in session; added path("set-language/", ...); exposes languages and current_language to profile template; shortened some feedback messages.
Templates / UI
src/templates/accounts/profile_edit.html, src/templates/partials/filters.html
Added "Language Preference" card and form posting to accounts:set_language with current language preselected; updated score option label to {{ value }}⭐ - {{ label }}.
Translations & strings
src/core/models.py, src/locale/fr/LC_MESSAGES/django.po
Switched gettextgettext_lazy; added French django.po translation catalog.
Tests
src/tests/accounts/test_views.py
Added TestSetLanguageView with tests for valid and invalid language POSTs and session behavior.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant Server
    participant Session
    participant LocaleMiddleware

    Note over User,Browser: User selects language and submits form
    User->>Browser: POST /accounts/set-language/ (language=fr)
    Browser->>Server: POST to set_language view
    Server->>Server: validate language (in settings.LANGUAGES)
    alt valid
        Server->>Session: set `django_language` = "fr"
        Server-->>Browser: 302 redirect to profile_edit
    else invalid
        Server-->>Browser: 302 redirect to profile_edit (no session change)
    end
    Browser->>Server: GET /accounts/profile_edit/ (with session cookie)
    Server->>LocaleMiddleware: read `django_language` from session
    LocaleMiddleware->>Server: activate language "fr"
    Server-->>Browser: render localized profile_edit HTML
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a pull request description explaining the purpose, scope, and any relevant context for implementing language switching and French translations.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding language switching functionality and French translation support across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
pyproject.toml (1)

55-57: Consider parameterizing the locale for future extensibility.

The translation tasks are correctly configured. However, the makemessages command hardcodes the French locale (-l fr). If you plan to support additional languages in the future, consider:

  1. Making the locale a parameter that can be passed at runtime
  2. Or creating separate tasks for each locale (e.g., makemessages-fr, makemessages-de)
🔎 Example with parameterized locale
 # Translations
-makemessages = "./src/manage.py makemessages -l fr --ignore=theme/* --ignore=staticfiles/* --ignore=venv/*"
+makemessages = "./src/manage.py makemessages --ignore=theme/* --ignore=staticfiles/* --ignore=venv/*"
+makemessages-fr = "./src/manage.py makemessages -l fr --ignore=theme/* --ignore=staticfiles/* --ignore=venv/*"
 compilemessages = "./src/manage.py compilemessages"

This keeps the French-specific task while adding a general task that respects Django's default behavior of updating all configured locales.

src/accounts/urls.py (1)

11-11: Restrict the set_language view to POST-only requests.

The set_language view modifies session state and should only accept POST requests. While the form in the template correctly uses POST, the URL pattern doesn't enforce this restriction, potentially allowing GET requests that would fail silently.

Add a decorator to the view in src/accounts/views.py:

from django.views.decorators.http import require_POST

@require_POST
@login_required
def set_language(request):
    # ... existing code
src/locale/fr/LC_MESSAGES/django.po (1)

1-19: Remove the fuzzy flag from the header.

The #, fuzzy flag on line 6 marks this catalog as incomplete/unverified. Some gettext configurations may skip fuzzy entries entirely. Remove this flag once translations are reviewed and ready for production.

🔎 Proposed fix
 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 #
-#, fuzzy
 msgid ""
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1af301d and 2600f53.

📒 Files selected for processing (9)
  • pyproject.toml
  • src/accounts/urls.py
  • src/accounts/views.py
  • src/config/settings.py
  • src/core/models.py
  • src/locale/fr/LC_MESSAGES/django.po
  • src/templates/accounts/profile_edit.html
  • src/templates/partials/filters.html
  • src/tests/accounts/test_views.py
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-12-27T18:00:03.595Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 23
File: src/templates/partials/score-readonly.html:2-33
Timestamp: 2025-12-27T18:00:03.595Z
Learning: In src/templates/partials/score-readonly.html, the DaisyUI read-only rating pattern correctly uses `aria-current="true"` on the selected star and applies the same color class (bg-orange-400) to all star elements. DaisyUI's CSS automatically handles which stars are visually filled based on the aria-current position.

Applied to files:

  • src/templates/partials/filters.html
📚 Learning: 2025-12-26T15:18:46.932Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 21
File: src/templates/accounts/profile_edit.html:23-58
Timestamp: 2025-12-26T15:18:46.932Z
Learning: In Django projects, attributes added to a form field's widget via field.widget.attrs.update(...) in the form's __init__ are rendered when using {{ form.field }} in templates. No explicit attribute definitions are needed in the template. This applies to templates under src/templates in Django apps; ensure you update attrs in __init__ for consistent HTMX behavior.

Applied to files:

  • src/templates/partials/filters.html
  • src/templates/accounts/profile_edit.html
📚 Learning: 2026-01-03T21:16:52.649Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 36
File: src/templates/partials/media-items.html:67-67
Timestamp: 2026-01-03T21:16:52.649Z
Learning: In HTML templates (e.g., src/templates/partials/media-items.html), Tailwind CSS v4.1.11 supports arbitrary numeric utilities (max-w-{n}, w-{n}, h-{n}) that map to calc(var(--spacing) * n). These should be considered valid Tailwind classes and not flagged as invalid. Ensure review tooling and linters treat such numeric utilities as allowed, and apply this guidance to other HTML/template files in the repository.

Applied to files:

  • src/templates/partials/filters.html
  • src/templates/accounts/profile_edit.html
📚 Learning: 2026-01-04T08:55:43.091Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 36
File: src/templates/partials/media-score-badge.html:1-9
Timestamp: 2026-01-04T08:55:43.091Z
Learning: In Django projects, configuring heroicons in TEMPLATES builtins makes heroicons.templatetags.heroicons available in all templates without needing {% load heroicons %}. For template reviews in this codebase, assume heroicon tags work globally in HTML templates under templates/, so avoid requiring explicit loads. If a template still uses {% load heroicons %}, assess whether the load is unnecessary and can be removed; ensure no conflicts arise from global tags.

Applied to files:

  • src/templates/partials/filters.html
  • src/templates/accounts/profile_edit.html
📚 Learning: 2026-01-02T14:50:26.110Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 32
File: src/config/urls.py:21-31
Timestamp: 2026-01-02T14:50:26.110Z
Learning: In Django projects, avoid using django.views.static.serve in production (i.e., when DEBUG is False). Review any urls.py configurations that route static or media files through this view and replace with a proper static file server (e.g., nginx/Apache) or use a dedicated tool like WhiteNoise for production-ready static file handling. This guidance applies to all Django url configurations, not just a single file.

Applied to files:

  • src/accounts/urls.py
🧬 Code graph analysis (2)
src/accounts/urls.py (1)
src/accounts/views.py (1)
  • set_language (84-97)
src/tests/accounts/test_views.py (1)
src/tests/conftest.py (1)
  • logged_in_client (65-68)
🔇 Additional comments (10)
src/core/models.py (1)

8-8: LGTM! Correct use of gettext_lazy for model translations.

This change correctly replaces gettext with gettext_lazy for model field translations. Since model classes are evaluated at module import time rather than at request time, lazy translation ensures that strings are translated according to each user's language preference when accessed.

src/templates/partials/filters.html (1)

42-42: LGTM! Nice UX improvement for score filtering.

Adding the star emoji and numeric value to the score filter options improves visual consistency and makes the filter more intuitive to use.

src/templates/accounts/profile_edit.html (1)

71-94: The Language Preference form has the required context variables.

The profile_edit view correctly provides both languages (from settings.LANGUAGES on line 47) and current_language (from translation.get_language() on line 48) in the template context, so the template will render correctly.

src/config/settings.py (2)

78-78: LGTM!

The LocaleMiddleware is correctly placed after SessionMiddleware and before CommonMiddleware, which is required for Django's language detection to work properly with session-based language preferences.


154-164: LGTM!

The i18n configuration is well-structured with proper LANGUAGES definition and LOCALE_PATHS setup pointing to the translation files directory.

src/tests/accounts/test_views.py (1)

125-141: LGTM!

The tests cover the core functionality well - valid language persistence and invalid language rejection.

Consider adding a test for the unauthenticated case if you want comprehensive coverage, though the @login_required decorator is well-tested by Django itself.

src/accounts/views.py (4)

1-1: LGTM!

Appropriate imports added for accessing settings.LANGUAGES and the translation module for language activation.

Also applies to: 7-7


27-27: LGTM!

Good use of _() (gettext_lazy) for translatable user-facing messages.

Also applies to: 35-35


38-48: LGTM!

The context now correctly exposes languages and current_language to the template for the language selection UI.


82-97: LGTM!

The set_language view implementation is solid:

  • Properly decorated with @require_POST and @login_required
  • Validates language against settings.LANGUAGES before activation
  • Uses Django's standard session key django_language for persistence
  • Provides user feedback via messages

The translation.activate() call ensures the success message itself is displayed in the newly selected language.

Comment thread src/locale/fr/LC_MESSAGES/django.po Outdated
Comment thread src/locale/fr/LC_MESSAGES/django.po Outdated
Comment thread src/locale/fr/LC_MESSAGES/django.po Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/locale/fr/LC_MESSAGES/django.po (1)

86-86: Address the missing and incorrect translations flagged in previous reviews.

The following translation issues were already identified in previous reviews:

  • Line 86: "Video game" has an empty msgstr
  • Line 202: "Adored" is translated to "In love" (English instead of French)
  • Line 239: "Upload cover:" has an empty msgstr

Please complete these translations as suggested in the previous review comments.

Also applies to: 202-202, 239-239

🧹 Nitpick comments (1)
src/locale/fr/LC_MESSAGES/django.po (1)

1-19: Consider updating the translation file metadata.

The header contains placeholder values (copyright, translator name/email, project version). While not critical for functionality, updating these improves maintainability and provides proper attribution.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2600f53 and 4e32720.

📒 Files selected for processing (9)
  • pyproject.toml
  • src/accounts/urls.py
  • src/accounts/views.py
  • src/config/settings.py
  • src/core/models.py
  • src/locale/fr/LC_MESSAGES/django.po
  • src/templates/accounts/profile_edit.html
  • src/templates/partials/filters.html
  • src/tests/accounts/test_views.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/templates/accounts/profile_edit.html
  • pyproject.toml
  • src/core/models.py
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-12-27T18:00:03.595Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 23
File: src/templates/partials/score-readonly.html:2-33
Timestamp: 2025-12-27T18:00:03.595Z
Learning: In src/templates/partials/score-readonly.html, the DaisyUI read-only rating pattern correctly uses `aria-current="true"` on the selected star and applies the same color class (bg-orange-400) to all star elements. DaisyUI's CSS automatically handles which stars are visually filled based on the aria-current position.

Applied to files:

  • src/templates/partials/filters.html
📚 Learning: 2025-12-26T15:18:46.932Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 21
File: src/templates/accounts/profile_edit.html:23-58
Timestamp: 2025-12-26T15:18:46.932Z
Learning: In Django projects, attributes added to a form field's widget via field.widget.attrs.update(...) in the form's __init__ are rendered when using {{ form.field }} in templates. No explicit attribute definitions are needed in the template. This applies to templates under src/templates in Django apps; ensure you update attrs in __init__ for consistent HTMX behavior.

Applied to files:

  • src/templates/partials/filters.html
📚 Learning: 2026-01-03T21:16:52.649Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 36
File: src/templates/partials/media-items.html:67-67
Timestamp: 2026-01-03T21:16:52.649Z
Learning: In HTML templates (e.g., src/templates/partials/media-items.html), Tailwind CSS v4.1.11 supports arbitrary numeric utilities (max-w-{n}, w-{n}, h-{n}) that map to calc(var(--spacing) * n). These should be considered valid Tailwind classes and not flagged as invalid. Ensure review tooling and linters treat such numeric utilities as allowed, and apply this guidance to other HTML/template files in the repository.

Applied to files:

  • src/templates/partials/filters.html
📚 Learning: 2026-01-04T08:55:43.091Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 36
File: src/templates/partials/media-score-badge.html:1-9
Timestamp: 2026-01-04T08:55:43.091Z
Learning: In Django projects, configuring heroicons in TEMPLATES builtins makes heroicons.templatetags.heroicons available in all templates without needing {% load heroicons %}. For template reviews in this codebase, assume heroicon tags work globally in HTML templates under templates/, so avoid requiring explicit loads. If a template still uses {% load heroicons %}, assess whether the load is unnecessary and can be removed; ensure no conflicts arise from global tags.

Applied to files:

  • src/templates/partials/filters.html
📚 Learning: 2026-01-02T14:50:26.110Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 32
File: src/config/urls.py:21-31
Timestamp: 2026-01-02T14:50:26.110Z
Learning: In Django projects, avoid using django.views.static.serve in production (i.e., when DEBUG is False). Review any urls.py configurations that route static or media files through this view and replace with a proper static file server (e.g., nginx/Apache) or use a dedicated tool like WhiteNoise for production-ready static file handling. This guidance applies to all Django url configurations, not just a single file.

Applied to files:

  • src/accounts/urls.py
🧬 Code graph analysis (3)
src/accounts/urls.py (1)
src/accounts/views.py (1)
  • set_language (84-97)
src/accounts/views.py (3)
src/accounts/forms.py (1)
  • CustomPasswordChangeForm (32-48)
src/tests/conftest.py (1)
  • user (53-61)
src/core/models.py (1)
  • save (213-226)
src/tests/accounts/test_views.py (1)
src/tests/conftest.py (1)
  • logged_in_client (65-68)
🔇 Additional comments (9)
src/templates/partials/filters.html (1)

42-42: LGTM! Score display enhancement.

The addition of the numeric value with star emoji ({{ value }}⭐) alongside the label improves clarity for users when filtering by score.

src/config/settings.py (2)

78-78: LGTM! Correct middleware placement.

LocaleMiddleware is correctly positioned after SessionMiddleware, which is required for session-based language selection to work properly.


154-164: LGTM! Standard Django i18n configuration.

The internationalization configuration is properly structured with supported languages and translation file paths. This aligns with Django's i18n best practices.

src/accounts/urls.py (1)

11-11: LGTM! Language switching route properly configured.

The new URL route correctly maps to the set_language view, which validates the language selection and stores it in the session using Django's standard django_language key.

src/tests/accounts/test_views.py (1)

125-144: LGTM! Good test coverage for language switching.

The tests properly cover both the valid language selection (storing "fr" in session) and invalid language rejection scenarios. The test assertions correctly verify the session state.

src/accounts/views.py (4)

1-1: LGTM! Standard i18n imports added.

The imports for settings and translation are correctly added to support language switching functionality.

Also applies to: 7-7


27-27: LGTM! Concise user feedback messages.

The shortened success messages are clear and properly wrapped with _() for translation support.

Also applies to: 35-35


38-40: LGTM! Language context properly exposed to template.

The current language and available languages are correctly retrieved and passed to the template context, enabling the language selection UI.

Also applies to: 47-48


82-97: LGTM! Solid implementation following Django i18n best practices.

The set_language view correctly implements language switching with:

  • Proper security decorators (@require_POST, @login_required)
  • Input validation against settings.LANGUAGES
  • Standard session key (django_language) for persistence
  • Immediate activation via translation.activate() so success messages display in the new language
  • Appropriate user feedback for both success and error cases

LocaleMiddleware is properly configured in settings and positioned after SessionMiddleware, so the view's session-based language persistence will work correctly on subsequent requests.

Comment thread src/locale/fr/LC_MESSAGES/django.po Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/accounts/views.py (1)

82-97: LGTM! Secure and correct language-switching implementation.

The view is properly protected with @require_POST and @login_required, validates the language code before activation, and correctly uses Django's standard django_language session key that LocaleMiddleware reads.

Optional: slightly more efficient language validation

For marginally better performance (though negligible with typical 2-5 languages), you could avoid creating a dict on each request:

-    if language and language in dict(settings.LANGUAGES):
+    if language and any(code == language for code, name in settings.LANGUAGES):

However, the current approach is more readable and the performance difference is insignificant.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e32720 and 63f0e5e.

📒 Files selected for processing (9)
  • pyproject.toml
  • src/accounts/urls.py
  • src/accounts/views.py
  • src/config/settings.py
  • src/core/models.py
  • src/locale/fr/LC_MESSAGES/django.po
  • src/templates/accounts/profile_edit.html
  • src/templates/partials/filters.html
  • src/tests/accounts/test_views.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/locale/fr/LC_MESSAGES/django.po
  • src/tests/accounts/test_views.py
  • pyproject.toml
  • src/config/settings.py
  • src/templates/partials/filters.html
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-26T15:18:46.932Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 21
File: src/templates/accounts/profile_edit.html:23-58
Timestamp: 2025-12-26T15:18:46.932Z
Learning: In Django projects, attributes added to a form field's widget via field.widget.attrs.update(...) in the form's __init__ are rendered when using {{ form.field }} in templates. No explicit attribute definitions are needed in the template. This applies to templates under src/templates in Django apps; ensure you update attrs in __init__ for consistent HTMX behavior.

Applied to files:

  • src/templates/accounts/profile_edit.html
📚 Learning: 2026-01-03T21:16:52.649Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 36
File: src/templates/partials/media-items.html:67-67
Timestamp: 2026-01-03T21:16:52.649Z
Learning: In HTML templates (e.g., src/templates/partials/media-items.html), Tailwind CSS v4.1.11 supports arbitrary numeric utilities (max-w-{n}, w-{n}, h-{n}) that map to calc(var(--spacing) * n). These should be considered valid Tailwind classes and not flagged as invalid. Ensure review tooling and linters treat such numeric utilities as allowed, and apply this guidance to other HTML/template files in the repository.

Applied to files:

  • src/templates/accounts/profile_edit.html
📚 Learning: 2026-01-04T08:55:43.091Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 36
File: src/templates/partials/media-score-badge.html:1-9
Timestamp: 2026-01-04T08:55:43.091Z
Learning: In Django projects, configuring heroicons in TEMPLATES builtins makes heroicons.templatetags.heroicons available in all templates without needing {% load heroicons %}. For template reviews in this codebase, assume heroicon tags work globally in HTML templates under templates/, so avoid requiring explicit loads. If a template still uses {% load heroicons %}, assess whether the load is unnecessary and can be removed; ensure no conflicts arise from global tags.

Applied to files:

  • src/templates/accounts/profile_edit.html
📚 Learning: 2026-01-02T14:50:26.110Z
Learnt from: PascalRepond
Repo: PascalRepond/datakult PR: 32
File: src/config/urls.py:21-31
Timestamp: 2026-01-02T14:50:26.110Z
Learning: In Django projects, avoid using django.views.static.serve in production (i.e., when DEBUG is False). Review any urls.py configurations that route static or media files through this view and replace with a proper static file server (e.g., nginx/Apache) or use a dedicated tool like WhiteNoise for production-ready static file handling. This guidance applies to all Django url configurations, not just a single file.

Applied to files:

  • src/accounts/urls.py
🧬 Code graph analysis (1)
src/accounts/urls.py (1)
src/accounts/views.py (1)
  • set_language (84-97)
🔇 Additional comments (5)
src/core/models.py (1)

8-8: LGTM! Correct i18n pattern for models.

Switching from gettext to gettext_lazy is the correct approach for model field definitions, ensuring translations are evaluated at render time using the request's language rather than at import time.

src/accounts/urls.py (1)

11-11: LGTM!

The URL pattern follows Django conventions and properly connects to the language-switching view.

src/templates/accounts/profile_edit.html (1)

71-94: LGTM! Well-integrated language selection UI.

The language preference card follows the existing design patterns, correctly posts to the set_language endpoint with CSRF protection, and properly uses the context variables provided by the view.

src/accounts/views.py (2)

1-1: LGTM! Necessary imports for i18n support.

The added imports provide access to language configuration and translation utilities required for the language-switching feature.

Also applies to: 7-7


38-48: LGTM! Correct language context integration.

The view properly retrieves the current language and exposes both available languages and the active language to the template, enabling the language selection UI.

@PascalRepond PascalRepond merged commit 16d180a into main Jan 4, 2026
3 checks passed
This was referenced Jan 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant