feat(locale): add language switching and French translation#37
feat(locale): add language switching and French translation#37PascalRepond merged 1 commit intomainfrom
Conversation
📝 WalkthroughWalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
Comment |
There was a problem hiding this comment.
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
makemessagescommand hardcodes the French locale (-l fr). If you plan to support additional languages in the future, consider:
- Making the locale a parameter that can be passed at runtime
- 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 theset_languageview to POST-only requests.The
set_languageview 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 codesrc/locale/fr/LC_MESSAGES/django.po (1)
1-19: Remove thefuzzyflag from the header.The
#, fuzzyflag 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
📒 Files selected for processing (9)
pyproject.tomlsrc/accounts/urls.pysrc/accounts/views.pysrc/config/settings.pysrc/core/models.pysrc/locale/fr/LC_MESSAGES/django.posrc/templates/accounts/profile_edit.htmlsrc/templates/partials/filters.htmlsrc/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.htmlsrc/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.htmlsrc/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.htmlsrc/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 ofgettext_lazyfor model translations.This change correctly replaces
gettextwithgettext_lazyfor 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_editview correctly provides bothlanguages(fromsettings.LANGUAGESon line 47) andcurrent_language(fromtranslation.get_language()on line 48) in the template context, so the template will render correctly.src/config/settings.py (2)
78-78: LGTM!The
LocaleMiddlewareis correctly placed afterSessionMiddlewareand beforeCommonMiddleware, 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
LANGUAGESdefinition andLOCALE_PATHSsetup 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_requireddecorator is well-tested by Django itself.src/accounts/views.py (4)
1-1: LGTM!Appropriate imports added for accessing
settings.LANGUAGESand thetranslationmodule 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
languagesandcurrent_languageto the template for the language selection UI.
82-97: LGTM!The
set_languageview implementation is solid:
- Properly decorated with
@require_POSTand@login_required- Validates language against
settings.LANGUAGESbefore activation- Uses Django's standard session key
django_languagefor persistence- Provides user feedback via messages
The
translation.activate()call ensures the success message itself is displayed in the newly selected language.
There was a problem hiding this comment.
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
msgstrPlease 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
📒 Files selected for processing (9)
pyproject.tomlsrc/accounts/urls.pysrc/accounts/views.pysrc/config/settings.pysrc/core/models.pysrc/locale/fr/LC_MESSAGES/django.posrc/templates/accounts/profile_edit.htmlsrc/templates/partials/filters.htmlsrc/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.
LocaleMiddlewareis correctly positioned afterSessionMiddleware, 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_languageview, which validates the language selection and stores it in the session using Django's standarddjango_languagekey.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
settingsandtranslationare 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_languageview 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
LocaleMiddlewareis properly configured in settings and positioned afterSessionMiddleware, so the view's session-based language persistence will work correctly on subsequent requests.
There was a problem hiding this comment.
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_POSTand@login_required, validates the language code before activation, and correctly uses Django's standarddjango_languagesession 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
📒 Files selected for processing (9)
pyproject.tomlsrc/accounts/urls.pysrc/accounts/views.pysrc/config/settings.pysrc/core/models.pysrc/locale/fr/LC_MESSAGES/django.posrc/templates/accounts/profile_edit.htmlsrc/templates/partials/filters.htmlsrc/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
gettexttogettext_lazyis 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.
No description provided.