# django-i18n-noprefix
[](https://github.com/jinto/django-i18n-noprefix/actions/workflows/test.yml)
[](https://github.com/jinto/django-i18n-noprefix/actions/workflows/quality.yml)
[](https://github.com/jinto/django-i18n-noprefix/actions/workflows/release.yml)
[](https://codecov.io/gh/jinto/django-i18n-noprefix)
[](https://pypi.org/project/django-i18n-noprefix/)
[](https://pypi.org/project/django-i18n-noprefix/)
[](https://www.djangoproject.com/)
[](https://github.com/jinto/django-i18n-noprefix/blob/main/LICENSE)
**Clean URLs for multilingual Django sites without language prefixes.**
Seamlessly integrate Django's i18n framework while keeping your URLs clean. No more `/en/about/` or `/ko/about/` — just `/about/` that works in any language.
## 🎯 Why django-i18n-noprefix?
Django's built-in i18n system automatically adds language codes to your URLs (`/en/`, `/ko/`, `/ja/`). While this works, it has drawbacks:
### The Problem with URL Prefixes
| Issue | With Prefixes | With django-i18n-noprefix |
|-------|--------------|---------------------------|
| **URLs** | `/en/products/`<br>`/ko/products/` | `/products/` |
| **SEO** | Duplicate content issues | Single URL per content |
| **Sharing** | Language-specific links | Universal links |
| **User Experience** | Exposes technical details | Clean, professional URLs |
### Real-World Example
```python
# Before: Django default i18n
https://mysite.com/en/products/laptop/ # English
https://mysite.com/ko/products/laptop/ # Korean
https://mysite.com/ja/products/laptop/ # Japanese
# After: With django-i18n-noprefix
https://mysite.com/products/laptop/ # Any language!
```
## ✨ Features
- 🚫 **No URL prefixes** — Keep URLs clean and professional
- 🔄 **Full Django i18n compatibility** — Use all Django's i18n features
- 🍪 **Smart language detection** — Session, cookie, and Accept-Language header support
- 🎨 **3 language selector styles** — Dropdown, list, or inline
- 🎯 **Framework agnostic** — Bootstrap, Tailwind, or vanilla CSS
- ⚡ **Zero configuration** — Works out of the box
- 🔒 **Production ready** — 93% test coverage
- 📦 **Lightweight** — No external dependencies except Django
## 🚀 Quick Start (5 minutes)
### 1. Install
```bash
pip install django-i18n-noprefix
```
### 2. Update Settings
```python
# settings.py
INSTALLED_APPS = [
# ...
'django_i18n_noprefix', # Add this
]
MIDDLEWARE = [
# ...
'django.contrib.sessions.middleware.SessionMiddleware',
# 'django.middleware.locale.LocaleMiddleware', # Remove this
'django_i18n_noprefix.middleware.NoPrefixLocaleMiddleware', # Add this
'django.middleware.common.CommonMiddleware',
# ...
]
# Configure languages
LANGUAGES = [
('en', 'English'),
('ko', '한국어'),
('ja', '日本語'),
]
LANGUAGE_CODE = 'en'
```
### 3. Add URL Pattern
```python
# urls.py
from django.urls import path, include
urlpatterns = [
# ...
path('i18n/', include('django_i18n_noprefix.urls')),
]
```
### 4. Add Language Selector
```django
<!-- base.html -->
{% load i18n_noprefix %}
<!DOCTYPE html>
<html>
<body>
<!-- Add anywhere in your template -->
{% language_selector %}
<!-- Your content -->
{% block content %}{% endblock %}
</body>
</html>
```
**That's it!** Your site now supports multiple languages without URL prefixes.
## 📋 Detailed Installation
### Using pip
```bash
pip install django-i18n-noprefix
```
### Using uv
```bash
uv pip install django-i18n-noprefix
```
### Development Installation
```bash
git clone https://github.com/jinto/django-i18n-noprefix.git
cd django-i18n-noprefix
pip install -e ".[dev]"
```
## 🔧 Configuration
### Basic Configuration
The minimal configuration shown in Quick Start is often sufficient. Here are all available options:
```python
# settings.py
# Required: Enable Django i18n
USE_I18N = True
USE_L10N = True # Django < 5.0
USE_TZ = True
# Language Configuration
LANGUAGES = [
('en', 'English'),
('ko', '한국어'),
('ja', '日本語'),
('zh', '中文'),
('es', 'Español'),
]
LANGUAGE_CODE = 'en' # Default language
# Optional: Customize cookie name (default: 'django_language')
LANGUAGE_COOKIE_NAME = 'django_language'
LANGUAGE_COOKIE_AGE = 365 * 24 * 60 * 60 # 1 year
LANGUAGE_COOKIE_PATH = '/'
LANGUAGE_COOKIE_DOMAIN = None
LANGUAGE_COOKIE_SECURE = False
LANGUAGE_COOKIE_HTTPONLY = False
LANGUAGE_COOKIE_SAMESITE = 'Lax'
```
### Language Detection Priority
Languages are detected in this order:
1. **URL parameter** (when switching languages)
2. **Session** (for logged-in users)
3. **Cookie** (for anonymous users)
4. **Accept-Language header** (browser preference)
5. **LANGUAGE_CODE setting** (fallback)
## 📖 Usage Examples
### Basic Language Selector
```django
{% load i18n_noprefix %}
<!-- Dropdown style (default) -->
{% language_selector %}
<!-- List style -->
{% language_selector style='list' %}
<!-- Inline style -->
{% language_selector style='inline' %}
```
### Custom Language Selector
```django
{% load i18n i18n_noprefix %}
{% get_current_language as CURRENT_LANGUAGE %}
{% get_available_languages as LANGUAGES %}
<!-- Custom dropdown -->
<select onchange="location.href=this.value">
{% for lang_code, lang_name in LANGUAGES %}
<option value="{% switch_language_url lang_code %}"
{% if lang_code == CURRENT_LANGUAGE %}selected{% endif %}>
{{ lang_name }}
</option>
{% endfor %}
</select>
<!-- Custom buttons -->
<div class="language-buttons">
{% for lang_code, lang_name in LANGUAGES %}
<a href="{% switch_language_url lang_code %}"
class="btn {% if lang_code|is_current_language %}active{% endif %}">
{{ lang_code|upper }}
</a>
{% endfor %}
</div>
```
### Programmatic Language Change
```python
# views.py
from django.shortcuts import redirect
from django_i18n_noprefix.utils import activate_language
def my_view(request):
# Change language programmatically
activate_language(request, 'ko')
return redirect('home')
# Or in class-based views
from django.views import View
class LanguagePreferenceView(View):
def post(self, request):
language = request.POST.get('language')
if language:
activate_language(request, language)
return redirect(request.META.get('HTTP_REFERER', '/'))
```
### AJAX Language Switching
```javascript
// JavaScript
function changeLanguage(langCode) {
fetch('/i18n/set-language-ajax/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({language: langCode})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
}
});
}
```
### Using with Django Forms
```python
# forms.py
from django import forms
from django.conf import settings
class LanguageForm(forms.Form):
language = forms.ChoiceField(
choices=settings.LANGUAGES,
widget=forms.Select(attrs={'class': 'form-control'})
)
# views.py
from django.shortcuts import redirect
from django_i18n_noprefix.utils import activate_language
def change_language_view(request):
if request.method == 'POST':
form = LanguageForm(request.POST)
if form.is_valid():
activate_language(request, form.cleaned_data['language'])
return redirect('home')
else:
form = LanguageForm()
return render(request, 'change_language.html', {'form': form})
```
## 🔄 Integration with Django i18n
This package **complements** Django's i18n system. You can use all Django i18n features:
### Using Django's Translation Features
```django
{% load i18n %}
<!-- Translation tags work normally -->
{% trans "Welcome" %}
{% blocktrans %}Hello {{ name }}{% endblocktrans %}
<!-- Get current language -->
{% get_current_language as LANGUAGE_CODE %}
Current language: {{ LANGUAGE_CODE }}
<!-- Get available languages -->
{% get_available_languages as LANGUAGES %}
```
### In Python Code
```python
from django.utils.translation import gettext as _
from django.utils.translation import get_language
def my_view(request):
# Django's translation functions work normally
message = _("Welcome to our site")
current_language = get_language()
return render(request, 'template.html', {
'message': message,
'language': current_language
})
```
### Migration from Standard Django i18n
Migrating from Django's default i18n is straightforward:
1. **Remove URL prefixes**:
```python
# Old: urls.py
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path('about/', views.about),
# ...
)
# New: urls.py
urlpatterns = [
path('about/', views.about),
# ...
]
```
2. **Replace middleware** (as shown in Quick Start)
3. **Update language switcher** (use our template tags)
That's it! All your translations and language files remain unchanged.
## 🎨 Styling Options
### Bootstrap 5
```django
{% load static %}
<link href="{% static 'i18n_noprefix/css/bootstrap5.css' %}" rel="stylesheet">
{% language_selector style='dropdown' %}
```
### Tailwind CSS
```django
{% load static %}
<link href="{% static 'i18n_noprefix/css/tailwind.css' %}" rel="stylesheet">
{% language_selector style='inline' %}
```
### Vanilla CSS
```django
{% load static %}
<link href="{% static 'i18n_noprefix/css/vanilla.css' %}" rel="stylesheet">
{% language_selector style='list' %}
```
### Custom Styling
```css
/* Override CSS variables */
:root {
--i18n-primary: #007bff;
--i18n-primary-hover: #0056b3;
--i18n-text: #333;
--i18n-bg: #fff;
--i18n-border: #ddd;
--i18n-radius: 4px;
}
/* Or write custom CSS */
.language-selector-dropdown {
/* Your styles */
}
```
## 📊 Django/Python Compatibility Matrix
| Django Version | Python 3.8 | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 |
|---------------|------------|------------|-------------|-------------|-------------|
| 4.2 LTS | ✅ | ✅ | ✅ | ✅ | ✅ |
| 5.0 | ❌ | ❌ | ✅ | ✅ | ✅ |
| 5.1 | ❌ | ❌ | ✅ | ✅ | ✅ |
| 5.2 | ❌ | ❌ | ✅ | ✅ | ✅ |
## 🎮 Example Project
See the package in action with our complete example project:
```bash
# Clone the repository
git clone https://github.com/jinto/django-i18n-noprefix.git
cd django-i18n-noprefix/example_project
# Install dependencies
pip install django
# Run the demo
./run_demo.sh
# Visit http://localhost:8000
```
The example project includes:
- Multi-language support (English, Korean, Japanese)
- All three language selector styles
- Bootstrap, Tailwind, and Vanilla CSS examples
- Complete translation files
- Production-ready configuration
## 🔍 API Reference
### Middleware
```python
class NoPrefixLocaleMiddleware:
"""
Replacement for Django's LocaleMiddleware that doesn't use URL prefixes.
Methods:
get_language(request) -> str: Detect language from request
save_language(request, response, language) -> None: Persist language choice
"""
```
### Template Tags
```django
{% load i18n_noprefix %}
<!-- Render language selector -->
{% language_selector [style='dropdown|list|inline'] [next_url='/custom/'] %}
<!-- Get URL for language switch -->
{% switch_language_url 'ko' [next_url='/custom/'] %}
<!-- Check if language is current -->
{{ 'ko'|is_current_language }} → True/False
```
### Views
```python
# URL: /i18n/set-language/<lang_code>/
def change_language(request, lang_code):
"""Change language and redirect."""
# URL: /i18n/set-language-ajax/
def set_language_ajax(request):
"""AJAX endpoint for language change."""
```
### Utility Functions
```python
from django_i18n_noprefix.utils import (
activate_language, # Activate language for request
get_supported_languages, # Get list of language codes
get_language_choices, # Get language choices for forms
is_valid_language, # Validate language code
)
```
## 🐛 Troubleshooting
### Common Issues and Solutions
**Language not persisting across requests**
- Ensure sessions are enabled in `INSTALLED_APPS` and `MIDDLEWARE`
- Check that cookies are not blocked in the browser
- Verify `NoPrefixLocaleMiddleware` is after `SessionMiddleware`
**Translations not working**
```python
# Check these settings
USE_I18N = True
LOCALE_PATHS = [BASE_DIR / 'locale']
# Run these commands
python manage.py makemessages -l ko
python manage.py compilemessages
```
**Static files not loading**
```bash
python manage.py collectstatic
```
**Middleware order issues**
```python
# Correct order
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # ← First
'django_i18n_noprefix.middleware.NoPrefixLocaleMiddleware', # ← Second
'django.middleware.common.CommonMiddleware',
# ...
]
```
### Debug Mode
```python
# Enable debug logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django_i18n_noprefix': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
```
## 🚀 Performance
- **Zero overhead**: Middleware adds < 0.1ms per request
- **Smart caching**: Language preference cached in session/cookie
- **No database queries**: Pure middleware solution
- **CDN friendly**: No URL prefixes mean better cache utilization
## 🔒 Security Considerations
- CSRF protection on language change endpoints
- XSS safe: All outputs are properly escaped
- Cookie security: Supports Secure, HttpOnly, and SameSite flags
- No user input in URL paths
## 🤝 Contributing
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
```bash
# Setup development environment
git clone https://github.com/jinto/django-i18n-noprefix.git
cd django-i18n-noprefix
pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=django_i18n_noprefix
# Code formatting
black .
ruff check .
# Type checking
mypy django_i18n_noprefix
```
## 📝 Changelog
See [CHANGELOG.md](CHANGELOG.md) for release history.
## 📄 License
MIT License - see [LICENSE](LICENSE) for details.
## 🙏 Credits
Built with ❤️ by [jinto](https://github.com/jinto) and contributors.
Inspired by Django's excellent i18n framework and the needs of real-world multilingual applications.
## 📚 Resources
- [Documentation](https://github.com/jinto/django-i18n-noprefix#readme)
- [PyPI Package](https://pypi.org/project/django-i18n-noprefix/)
- [Issue Tracker](https://github.com/jinto/django-i18n-noprefix/issues)
- [Example Project](https://github.com/jinto/django-i18n-noprefix/tree/main/example_project)
- [Django i18n Documentation](https://docs.djangoproject.com/en/stable/topics/i18n/)
---
**Need help?** [Open an issue](https://github.com/jinto/django-i18n-noprefix/issues) or check our [example project](https://github.com/jinto/django-i18n-noprefix/tree/main/example_project).
**Like this project?** Give it a ⭐ on [GitHub](https://github.com/jinto/django-i18n-noprefix)!
Raw data
{
"_id": null,
"home_page": null,
"name": "django-i18n-noprefix",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "django, i18n, internationalization, l10n, localization, no-prefix, translation, url",
"author": "jinto",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/54/61/bf6825d32be833902f56c1b4c613e991d90d8b95ae4bd164985303118cfe/django_i18n_noprefix-0.1.1.tar.gz",
"platform": null,
"description": "# django-i18n-noprefix\n\n[](https://github.com/jinto/django-i18n-noprefix/actions/workflows/test.yml)\n[](https://github.com/jinto/django-i18n-noprefix/actions/workflows/quality.yml)\n[](https://github.com/jinto/django-i18n-noprefix/actions/workflows/release.yml)\n[](https://codecov.io/gh/jinto/django-i18n-noprefix)\n[](https://pypi.org/project/django-i18n-noprefix/)\n[](https://pypi.org/project/django-i18n-noprefix/)\n[](https://www.djangoproject.com/)\n[](https://github.com/jinto/django-i18n-noprefix/blob/main/LICENSE)\n\n**Clean URLs for multilingual Django sites without language prefixes.**\n\nSeamlessly integrate Django's i18n framework while keeping your URLs clean. No more `/en/about/` or `/ko/about/` \u2014 just `/about/` that works in any language.\n\n## \ud83c\udfaf Why django-i18n-noprefix?\n\nDjango's built-in i18n system automatically adds language codes to your URLs (`/en/`, `/ko/`, `/ja/`). While this works, it has drawbacks:\n\n### The Problem with URL Prefixes\n\n| Issue | With Prefixes | With django-i18n-noprefix |\n|-------|--------------|---------------------------|\n| **URLs** | `/en/products/`<br>`/ko/products/` | `/products/` |\n| **SEO** | Duplicate content issues | Single URL per content |\n| **Sharing** | Language-specific links | Universal links |\n| **User Experience** | Exposes technical details | Clean, professional URLs |\n\n### Real-World Example\n\n```python\n# Before: Django default i18n\nhttps://mysite.com/en/products/laptop/ # English\nhttps://mysite.com/ko/products/laptop/ # Korean\nhttps://mysite.com/ja/products/laptop/ # Japanese\n\n# After: With django-i18n-noprefix\nhttps://mysite.com/products/laptop/ # Any language!\n```\n\n## \u2728 Features\n\n- \ud83d\udeab **No URL prefixes** \u2014 Keep URLs clean and professional\n- \ud83d\udd04 **Full Django i18n compatibility** \u2014 Use all Django's i18n features\n- \ud83c\udf6a **Smart language detection** \u2014 Session, cookie, and Accept-Language header support\n- \ud83c\udfa8 **3 language selector styles** \u2014 Dropdown, list, or inline\n- \ud83c\udfaf **Framework agnostic** \u2014 Bootstrap, Tailwind, or vanilla CSS\n- \u26a1 **Zero configuration** \u2014 Works out of the box\n- \ud83d\udd12 **Production ready** \u2014 93% test coverage\n- \ud83d\udce6 **Lightweight** \u2014 No external dependencies except Django\n\n## \ud83d\ude80 Quick Start (5 minutes)\n\n### 1. Install\n\n```bash\npip install django-i18n-noprefix\n```\n\n### 2. Update Settings\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n # ...\n 'django_i18n_noprefix', # Add this\n]\n\nMIDDLEWARE = [\n # ...\n 'django.contrib.sessions.middleware.SessionMiddleware',\n # 'django.middleware.locale.LocaleMiddleware', # Remove this\n 'django_i18n_noprefix.middleware.NoPrefixLocaleMiddleware', # Add this\n 'django.middleware.common.CommonMiddleware',\n # ...\n]\n\n# Configure languages\nLANGUAGES = [\n ('en', 'English'),\n ('ko', '\ud55c\uad6d\uc5b4'),\n ('ja', '\u65e5\u672c\u8a9e'),\n]\nLANGUAGE_CODE = 'en'\n```\n\n### 3. Add URL Pattern\n\n```python\n# urls.py\nfrom django.urls import path, include\n\nurlpatterns = [\n # ...\n path('i18n/', include('django_i18n_noprefix.urls')),\n]\n```\n\n### 4. Add Language Selector\n\n```django\n<!-- base.html -->\n{% load i18n_noprefix %}\n\n<!DOCTYPE html>\n<html>\n<body>\n <!-- Add anywhere in your template -->\n {% language_selector %}\n\n <!-- Your content -->\n {% block content %}{% endblock %}\n</body>\n</html>\n```\n\n**That's it!** Your site now supports multiple languages without URL prefixes.\n\n## \ud83d\udccb Detailed Installation\n\n### Using pip\n\n```bash\npip install django-i18n-noprefix\n```\n\n### Using uv\n\n```bash\nuv pip install django-i18n-noprefix\n```\n\n### Development Installation\n\n```bash\ngit clone https://github.com/jinto/django-i18n-noprefix.git\ncd django-i18n-noprefix\npip install -e \".[dev]\"\n```\n\n## \ud83d\udd27 Configuration\n\n### Basic Configuration\n\nThe minimal configuration shown in Quick Start is often sufficient. Here are all available options:\n\n```python\n# settings.py\n\n# Required: Enable Django i18n\nUSE_I18N = True\nUSE_L10N = True # Django < 5.0\nUSE_TZ = True\n\n# Language Configuration\nLANGUAGES = [\n ('en', 'English'),\n ('ko', '\ud55c\uad6d\uc5b4'),\n ('ja', '\u65e5\u672c\u8a9e'),\n ('zh', '\u4e2d\u6587'),\n ('es', 'Espa\u00f1ol'),\n]\n\nLANGUAGE_CODE = 'en' # Default language\n\n# Optional: Customize cookie name (default: 'django_language')\nLANGUAGE_COOKIE_NAME = 'django_language'\nLANGUAGE_COOKIE_AGE = 365 * 24 * 60 * 60 # 1 year\nLANGUAGE_COOKIE_PATH = '/'\nLANGUAGE_COOKIE_DOMAIN = None\nLANGUAGE_COOKIE_SECURE = False\nLANGUAGE_COOKIE_HTTPONLY = False\nLANGUAGE_COOKIE_SAMESITE = 'Lax'\n```\n\n### Language Detection Priority\n\nLanguages are detected in this order:\n\n1. **URL parameter** (when switching languages)\n2. **Session** (for logged-in users)\n3. **Cookie** (for anonymous users)\n4. **Accept-Language header** (browser preference)\n5. **LANGUAGE_CODE setting** (fallback)\n\n## \ud83d\udcd6 Usage Examples\n\n### Basic Language Selector\n\n```django\n{% load i18n_noprefix %}\n\n<!-- Dropdown style (default) -->\n{% language_selector %}\n\n<!-- List style -->\n{% language_selector style='list' %}\n\n<!-- Inline style -->\n{% language_selector style='inline' %}\n```\n\n### Custom Language Selector\n\n```django\n{% load i18n i18n_noprefix %}\n{% get_current_language as CURRENT_LANGUAGE %}\n{% get_available_languages as LANGUAGES %}\n\n<!-- Custom dropdown -->\n<select onchange=\"location.href=this.value\">\n {% for lang_code, lang_name in LANGUAGES %}\n <option value=\"{% switch_language_url lang_code %}\"\n {% if lang_code == CURRENT_LANGUAGE %}selected{% endif %}>\n {{ lang_name }}\n </option>\n {% endfor %}\n</select>\n\n<!-- Custom buttons -->\n<div class=\"language-buttons\">\n {% for lang_code, lang_name in LANGUAGES %}\n <a href=\"{% switch_language_url lang_code %}\"\n class=\"btn {% if lang_code|is_current_language %}active{% endif %}\">\n {{ lang_code|upper }}\n </a>\n {% endfor %}\n</div>\n```\n\n### Programmatic Language Change\n\n```python\n# views.py\nfrom django.shortcuts import redirect\nfrom django_i18n_noprefix.utils import activate_language\n\ndef my_view(request):\n # Change language programmatically\n activate_language(request, 'ko')\n return redirect('home')\n\n# Or in class-based views\nfrom django.views import View\n\nclass LanguagePreferenceView(View):\n def post(self, request):\n language = request.POST.get('language')\n if language:\n activate_language(request, language)\n return redirect(request.META.get('HTTP_REFERER', '/'))\n```\n\n### AJAX Language Switching\n\n```javascript\n// JavaScript\nfunction changeLanguage(langCode) {\n fetch('/i18n/set-language-ajax/', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRFToken': getCookie('csrftoken')\n },\n body: JSON.stringify({language: langCode})\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n location.reload();\n }\n });\n}\n```\n\n### Using with Django Forms\n\n```python\n# forms.py\nfrom django import forms\nfrom django.conf import settings\n\nclass LanguageForm(forms.Form):\n language = forms.ChoiceField(\n choices=settings.LANGUAGES,\n widget=forms.Select(attrs={'class': 'form-control'})\n )\n\n# views.py\nfrom django.shortcuts import redirect\nfrom django_i18n_noprefix.utils import activate_language\n\ndef change_language_view(request):\n if request.method == 'POST':\n form = LanguageForm(request.POST)\n if form.is_valid():\n activate_language(request, form.cleaned_data['language'])\n return redirect('home')\n else:\n form = LanguageForm()\n return render(request, 'change_language.html', {'form': form})\n```\n\n## \ud83d\udd04 Integration with Django i18n\n\nThis package **complements** Django's i18n system. You can use all Django i18n features:\n\n### Using Django's Translation Features\n\n```django\n{% load i18n %}\n\n<!-- Translation tags work normally -->\n{% trans \"Welcome\" %}\n{% blocktrans %}Hello {{ name }}{% endblocktrans %}\n\n<!-- Get current language -->\n{% get_current_language as LANGUAGE_CODE %}\nCurrent language: {{ LANGUAGE_CODE }}\n\n<!-- Get available languages -->\n{% get_available_languages as LANGUAGES %}\n```\n\n### In Python Code\n\n```python\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import get_language\n\ndef my_view(request):\n # Django's translation functions work normally\n message = _(\"Welcome to our site\")\n current_language = get_language()\n\n return render(request, 'template.html', {\n 'message': message,\n 'language': current_language\n })\n```\n\n### Migration from Standard Django i18n\n\nMigrating from Django's default i18n is straightforward:\n\n1. **Remove URL prefixes**:\n ```python\n # Old: urls.py\n from django.conf.urls.i18n import i18n_patterns\n\n urlpatterns = i18n_patterns(\n path('about/', views.about),\n # ...\n )\n\n # New: urls.py\n urlpatterns = [\n path('about/', views.about),\n # ...\n ]\n ```\n\n2. **Replace middleware** (as shown in Quick Start)\n\n3. **Update language switcher** (use our template tags)\n\nThat's it! All your translations and language files remain unchanged.\n\n## \ud83c\udfa8 Styling Options\n\n### Bootstrap 5\n\n```django\n{% load static %}\n<link href=\"{% static 'i18n_noprefix/css/bootstrap5.css' %}\" rel=\"stylesheet\">\n\n{% language_selector style='dropdown' %}\n```\n\n### Tailwind CSS\n\n```django\n{% load static %}\n<link href=\"{% static 'i18n_noprefix/css/tailwind.css' %}\" rel=\"stylesheet\">\n\n{% language_selector style='inline' %}\n```\n\n### Vanilla CSS\n\n```django\n{% load static %}\n<link href=\"{% static 'i18n_noprefix/css/vanilla.css' %}\" rel=\"stylesheet\">\n\n{% language_selector style='list' %}\n```\n\n### Custom Styling\n\n```css\n/* Override CSS variables */\n:root {\n --i18n-primary: #007bff;\n --i18n-primary-hover: #0056b3;\n --i18n-text: #333;\n --i18n-bg: #fff;\n --i18n-border: #ddd;\n --i18n-radius: 4px;\n}\n\n/* Or write custom CSS */\n.language-selector-dropdown {\n /* Your styles */\n}\n```\n\n## \ud83d\udcca Django/Python Compatibility Matrix\n\n| Django Version | Python 3.8 | Python 3.9 | Python 3.10 | Python 3.11 | Python 3.12 |\n|---------------|------------|------------|-------------|-------------|-------------|\n| 4.2 LTS | \u2705 | \u2705 | \u2705 | \u2705 | \u2705 |\n| 5.0 | \u274c | \u274c | \u2705 | \u2705 | \u2705 |\n| 5.1 | \u274c | \u274c | \u2705 | \u2705 | \u2705 |\n| 5.2 | \u274c | \u274c | \u2705 | \u2705 | \u2705 |\n\n## \ud83c\udfae Example Project\n\nSee the package in action with our complete example project:\n\n```bash\n# Clone the repository\ngit clone https://github.com/jinto/django-i18n-noprefix.git\ncd django-i18n-noprefix/example_project\n\n# Install dependencies\npip install django\n\n# Run the demo\n./run_demo.sh\n\n# Visit http://localhost:8000\n```\n\nThe example project includes:\n- Multi-language support (English, Korean, Japanese)\n- All three language selector styles\n- Bootstrap, Tailwind, and Vanilla CSS examples\n- Complete translation files\n- Production-ready configuration\n\n## \ud83d\udd0d API Reference\n\n### Middleware\n\n```python\nclass NoPrefixLocaleMiddleware:\n \"\"\"\n Replacement for Django's LocaleMiddleware that doesn't use URL prefixes.\n\n Methods:\n get_language(request) -> str: Detect language from request\n save_language(request, response, language) -> None: Persist language choice\n \"\"\"\n```\n\n### Template Tags\n\n```django\n{% load i18n_noprefix %}\n\n<!-- Render language selector -->\n{% language_selector [style='dropdown|list|inline'] [next_url='/custom/'] %}\n\n<!-- Get URL for language switch -->\n{% switch_language_url 'ko' [next_url='/custom/'] %}\n\n<!-- Check if language is current -->\n{{ 'ko'|is_current_language }} \u2192 True/False\n```\n\n### Views\n\n```python\n# URL: /i18n/set-language/<lang_code>/\ndef change_language(request, lang_code):\n \"\"\"Change language and redirect.\"\"\"\n\n# URL: /i18n/set-language-ajax/\ndef set_language_ajax(request):\n \"\"\"AJAX endpoint for language change.\"\"\"\n```\n\n### Utility Functions\n\n```python\nfrom django_i18n_noprefix.utils import (\n activate_language, # Activate language for request\n get_supported_languages, # Get list of language codes\n get_language_choices, # Get language choices for forms\n is_valid_language, # Validate language code\n)\n```\n\n## \ud83d\udc1b Troubleshooting\n\n### Common Issues and Solutions\n\n**Language not persisting across requests**\n- Ensure sessions are enabled in `INSTALLED_APPS` and `MIDDLEWARE`\n- Check that cookies are not blocked in the browser\n- Verify `NoPrefixLocaleMiddleware` is after `SessionMiddleware`\n\n**Translations not working**\n```python\n# Check these settings\nUSE_I18N = True\nLOCALE_PATHS = [BASE_DIR / 'locale']\n\n# Run these commands\npython manage.py makemessages -l ko\npython manage.py compilemessages\n```\n\n**Static files not loading**\n```bash\npython manage.py collectstatic\n```\n\n**Middleware order issues**\n```python\n# Correct order\nMIDDLEWARE = [\n 'django.middleware.security.SecurityMiddleware',\n 'django.contrib.sessions.middleware.SessionMiddleware', # \u2190 First\n 'django_i18n_noprefix.middleware.NoPrefixLocaleMiddleware', # \u2190 Second\n 'django.middleware.common.CommonMiddleware',\n # ...\n]\n```\n\n### Debug Mode\n\n```python\n# Enable debug logging\nLOGGING = {\n 'version': 1,\n 'disable_existing_loggers': False,\n 'handlers': {\n 'console': {\n 'class': 'logging.StreamHandler',\n },\n },\n 'loggers': {\n 'django_i18n_noprefix': {\n 'handlers': ['console'],\n 'level': 'DEBUG',\n },\n },\n}\n```\n\n## \ud83d\ude80 Performance\n\n- **Zero overhead**: Middleware adds < 0.1ms per request\n- **Smart caching**: Language preference cached in session/cookie\n- **No database queries**: Pure middleware solution\n- **CDN friendly**: No URL prefixes mean better cache utilization\n\n## \ud83d\udd12 Security Considerations\n\n- CSRF protection on language change endpoints\n- XSS safe: All outputs are properly escaped\n- Cookie security: Supports Secure, HttpOnly, and SameSite flags\n- No user input in URL paths\n\n## \ud83e\udd1d Contributing\n\nWe welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n```bash\n# Setup development environment\ngit clone https://github.com/jinto/django-i18n-noprefix.git\ncd django-i18n-noprefix\npip install -e \".[dev]\"\n\n# Run tests\npytest\n\n# Run with coverage\npytest --cov=django_i18n_noprefix\n\n# Code formatting\nblack .\nruff check .\n\n# Type checking\nmypy django_i18n_noprefix\n```\n\n## \ud83d\udcdd Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for release history.\n\n## \ud83d\udcc4 License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n## \ud83d\ude4f Credits\n\nBuilt with \u2764\ufe0f by [jinto](https://github.com/jinto) and contributors.\n\nInspired by Django's excellent i18n framework and the needs of real-world multilingual applications.\n\n## \ud83d\udcda Resources\n\n- [Documentation](https://github.com/jinto/django-i18n-noprefix#readme)\n- [PyPI Package](https://pypi.org/project/django-i18n-noprefix/)\n- [Issue Tracker](https://github.com/jinto/django-i18n-noprefix/issues)\n- [Example Project](https://github.com/jinto/django-i18n-noprefix/tree/main/example_project)\n- [Django i18n Documentation](https://docs.djangoproject.com/en/stable/topics/i18n/)\n\n---\n\n**Need help?** [Open an issue](https://github.com/jinto/django-i18n-noprefix/issues) or check our [example project](https://github.com/jinto/django-i18n-noprefix/tree/main/example_project).\n\n**Like this project?** Give it a \u2b50 on [GitHub](https://github.com/jinto/django-i18n-noprefix)!\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Django internationalization without URL prefixes",
"version": "0.1.1",
"project_urls": {
"Changelog": "https://github.com/jinto/django-i18n-noprefix/blob/main/CHANGELOG.md",
"Documentation": "https://github.com/jinto/django-i18n-noprefix#readme",
"Homepage": "https://github.com/jinto/django-i18n-noprefix",
"Issues": "https://github.com/jinto/django-i18n-noprefix/issues",
"Repository": "https://github.com/jinto/django-i18n-noprefix.git"
},
"split_keywords": [
"django",
" i18n",
" internationalization",
" l10n",
" localization",
" no-prefix",
" translation",
" url"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "dddd7a39a2d61be9209c675587c210d384afbfbcfeefb6f01707f1c260ddef52",
"md5": "99873fd86abaf90580c79bacf55110eb",
"sha256": "92a4d9bc448dfd0dff16a5927324901690fd0bd997540d92cb0233cbfa932b59"
},
"downloads": -1,
"filename": "django_i18n_noprefix-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "99873fd86abaf90580c79bacf55110eb",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 38354,
"upload_time": "2025-08-08T05:54:18",
"upload_time_iso_8601": "2025-08-08T05:54:18.242342Z",
"url": "https://files.pythonhosted.org/packages/dd/dd/7a39a2d61be9209c675587c210d384afbfbcfeefb6f01707f1c260ddef52/django_i18n_noprefix-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5461bf6825d32be833902f56c1b4c613e991d90d8b95ae4bd164985303118cfe",
"md5": "d92e397b35353d1bd1ed5a7c9792ebaf",
"sha256": "a982afa7ac0621690b4f51964b6c90b5f9256f25e917af2c0eb5f9ed2cc5a951"
},
"downloads": -1,
"filename": "django_i18n_noprefix-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "d92e397b35353d1bd1ed5a7c9792ebaf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 31237,
"upload_time": "2025-08-08T05:54:19",
"upload_time_iso_8601": "2025-08-08T05:54:19.565588Z",
"url": "https://files.pythonhosted.org/packages/54/61/bf6825d32be833902f56c1b4c613e991d90d8b95ae4bd164985303118cfe/django_i18n_noprefix-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-08 05:54:19",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jinto",
"github_project": "django-i18n-noprefix",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "django-i18n-noprefix"
}