Skip to content

Commit af4f9e6

Browse files
authored
Merge pull request #366 from adamspd/361-i18n-implementation
Implement i18n/l10n with French translation and UX enhancements
2 parents 007632e + a042c51 commit af4f9e6

26 files changed

+3439
-335
lines changed

.gitignore

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ coverage.xml
6767
.pytest_cache/
6868
cover/
6969

70-
# Translations
71-
*.mo
72-
*.pot
73-
7470
# Django stuff:
7571
*.log
7672
local_settings.py
@@ -178,4 +174,7 @@ cython_debug/
178174
**/.DS_Store/**
179175
**/migrations/**
180176
/services/
181-
locale/
177+
data
178+
179+
*.pyc
180+
*.pyx

INTERNATIONALIZATION.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Internationalization (i18n) Guide 🌍
2+
3+
Django-Appointment includes built-in internationalization support with localized date formats, translations, and multi-language capabilities.
4+
5+
## Built-in Language Support 🗣️
6+
7+
Django-Appointment currently includes translations for:
8+
- **English** (en) - Default
9+
- **French** (fr) - Complete translation
10+
11+
## Quick Setup
12+
13+
### 1. Enable Internationalization in Django
14+
15+
Add these settings to your `settings.py`:
16+
17+
```python
18+
# Internationalization
19+
LANGUAGE_CODE = 'en' # Default language
20+
USE_I18N = True # Enable translations
21+
USE_L10N = True # Enable localized formatting
22+
USE_TZ = True # Enable timezone support
23+
24+
# Supported languages
25+
LANGUAGES = [
26+
('en', 'English'),
27+
('fr', 'French'),
28+
# Add more languages as needed
29+
]
30+
31+
# Translation files location
32+
LOCALE_PATHS = [
33+
BASE_DIR / 'locale',
34+
]
35+
```
36+
37+
### 2. Add Middleware
38+
39+
Add the locale middleware to your `MIDDLEWARE` setting:
40+
41+
```python
42+
MIDDLEWARE = [
43+
# ... other middleware
44+
'django.middleware.locale.LocaleMiddleware',
45+
# ... other middleware
46+
]
47+
```
48+
49+
### 3. Language Switching
50+
51+
Include language switching in your URLs:
52+
53+
```python
54+
# urls.py
55+
from django.conf.urls.i18n import i18n_patterns
56+
from django.urls import path, include
57+
58+
urlpatterns = [
59+
path('i18n/', include('django.conf.urls.i18n')),
60+
]
61+
62+
urlpatterns += i18n_patterns(
63+
path('appointment/', include('appointment.urls')),
64+
# ... other URL patterns
65+
)
66+
```
67+
68+
## Localized Date Formats 📅
69+
70+
Django-Appointment automatically formats dates according to the user's language:
71+
72+
- **English**: "Thu, August 14, 2025"
73+
- **French**: "jeu 14 août 2025"
74+
- **German**: "Do, 14. August 2025"
75+
- **Spanish**: "jue, 14 de agosto de 2025"
76+
77+
The package includes date format patterns for 35+ languages. No additional configuration needed!
78+
79+
## Contributing Translations 🤝
80+
81+
Want to add support for your language? We'd love your help!
82+
83+
### Adding a New Language
84+
85+
1. **Fork the repository** and create a new branch
86+
2. **Generate translation files**:
87+
```bash
88+
python manage.py makemessages -l [language_code]
89+
# Example: python manage.py makemessages -l es
90+
```
91+
3. **Translate the strings** in `locale/[language_code]/LC_MESSAGES/django.po`
92+
4. **Add date format** to `appointment/utils/date_time.py` in the `DATE_FORMATS` dictionary
93+
5. **Test your translations**:
94+
```bash
95+
python manage.py compilemessages
96+
```
97+
6. **Submit a pull request**
98+
99+
### Translation Guidelines
100+
101+
- Use formal tone for UI elements
102+
- Keep technical terms consistent
103+
- Test date formats with real examples
104+
- Include gender-neutral language where possible
105+
106+
## Advanced: Translating Database Content 🗃️
107+
108+
For translating service names, descriptions, and other database content, you can use third-party packages:
109+
110+
### Option 1: django-modeltranslation
111+
112+
1. **Install the package**:
113+
```bash
114+
pip install django-modeltranslation
115+
```
116+
117+
2. **Add to INSTALLED_APPS**:
118+
```python
119+
INSTALLED_APPS = [
120+
'modeltranslation',
121+
'appointment', # Must come after modeltranslation
122+
# ... other apps
123+
]
124+
```
125+
126+
3. **Create translation configuration**:
127+
```python
128+
# translation.py (in your project root)
129+
from modeltranslation.translator import translator, TranslationOptions
130+
from appointment.models import Service
131+
132+
class ServiceTranslationOptions(TranslationOptions):
133+
fields = ('name', 'description')
134+
135+
translator.register(Service, ServiceTranslationOptions)
136+
```
137+
138+
4. **Generate and run migrations**:
139+
```bash
140+
python manage.py makemigrations
141+
python manage.py migrate
142+
```
143+
144+
### Option 2: django-parler
145+
146+
1. **Install the package**:
147+
```bash
148+
pip install django-parler
149+
```
150+
151+
2. **Follow django-parler documentation** for setup and configuration
152+
153+
## Language-Specific Features 🎯
154+
155+
### Right-to-Left (RTL) Languages
156+
157+
Django-Appointment includes basic RTL support for languages like Arabic and Hebrew. The date formats are properly configured for RTL display.
158+
159+
### Pluralization
160+
161+
The package correctly handles plural forms for time durations:
162+
- English: "1 hour" vs "2 hours"
163+
- French: "1 heure" vs "2 heures"
164+
- And more complex rules for languages like Russian or Arabic
165+
166+
## Troubleshooting 🔧
167+
168+
### Common Issues
169+
170+
1. **Dates showing in wrong format**:
171+
- Ensure `USE_L10N = True` in settings
172+
- Check that locale middleware is enabled
173+
- Verify language code is supported
174+
175+
2. **Translations not appearing**:
176+
- Run `python manage.py compilemessages`
177+
- Check `LOCALE_PATHS` setting
178+
- Verify middleware order
179+
180+
3. **Mixed language content**:
181+
- Database content requires separate translation (see above)
182+
- UI elements use Django's translation system
183+
184+
### Getting Help
185+
186+
- Check the [main documentation](https://django-appt-doc.adamspierredavid.com)
187+
- Open an issue on [GitHub](https://github.com/adamspd/django-appointment/issues)
188+
- Join the discussion in our [community](https://github.com/adamspd/django-appointment/discussions)
189+
190+
## Supported Date Format Languages 📊
191+
192+
Currently includes date format patterns for:
193+
194+
Arabic, Bengali, Bulgarian, Chinese, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hebrew, Hindi, Croatian, Hungarian, Indonesian, Italian, Japanese, Korean, Latvian, Lithuanian, Malay, Norwegian, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Serbian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese
195+
196+
Missing your language? Please contribute!

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ recursive-include appointment/static/img *.jpg
2929
recursive-include appointment/templates/ *.html
3030
recursive-include appointment/tests *.py
3131
recursive-include appointment/utils *.py
32+
recursive-include appointment/locale *.po *.mo
3233

3334
# Exclusions
3435
exclude release_notes.md

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,31 @@ See an example of a base.html template [here](https://github.com/adamspd/django-
275275
2. Modify these values as needed for your application, and the app will adapt to the new settings.
276276
3. For further customization, you can extend the provided models, views, and templates or create your own.
277277

278+
## Internationalization 🌍
279+
280+
Django-Appointment includes built-in support for multiple languages with localized date formats and translations.
281+
282+
**Currently supported languages:**
283+
- English (en) - Default
284+
- French (fr) - Complete translation
285+
286+
**Features:**
287+
- 🗣️ UI translations for 2 languages (more welcome!)
288+
- 📅 Localized date formats for 35+ languages
289+
- 🌐 Automatic language detection
290+
- 📝 RTL language support
291+
292+
**Quick setup:**
293+
```python
294+
# settings.py
295+
USE_I18N = True
296+
USE_L10N = True
297+
LANGUAGES = [('en', 'English'), ('fr', 'French')]
298+
```
299+
300+
**Want to add your language or translate database content?**
301+
👉 **[See the full Internationalization Guide](INTERNATIONALIZATION.md)**
302+
278303
## Docker Support 🐳
279304

280305
Django-Appointment now supports Docker, making it easier to set up, develop, and test.

appointment/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
__url__ = "https://github.com/adamspd/django-appointment"
77
__package_website__ = "https://django-appt.adamspierredavid.com/"
88
__package_doc_url__ = "https://django-appt-doc.adamspierredavid.com/"
9-
__version__ = "3.8.0"
10-
__test_version__ = False
9+
__version__ = "3.9.0"
10+
__test_version__ = True

appointment/forms.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020

2121
class SlotForm(forms.Form):
2222
selected_date = forms.DateField(validators=[not_in_the_past])
23-
staff_member = forms.ModelChoiceField(StaffMember.objects.all(),
24-
error_messages={'invalid_choice': 'Staff member does not exist'})
23+
staff_member = forms.ModelChoiceField(
24+
StaffMember.objects.all(),
25+
error_messages={'invalid_choice': _('Staff member does not exist')}
26+
)
2527

2628

2729
class AppointmentRequestForm(forms.ModelForm):
@@ -35,7 +37,7 @@ class Meta:
3537
model = AppointmentRescheduleHistory
3638
fields = ['reason_for_rescheduling']
3739
widgets = {
38-
'reason_for_rescheduling': forms.Textarea(attrs={'rows': 4, 'placeholder': 'Reason for rescheduling...'}),
40+
'reason_for_rescheduling': forms.Textarea(attrs={'rows': 4, 'placeholder': _('Reason for rescheduling...')}),
3941
}
4042

4143

@@ -50,7 +52,7 @@ def __init__(self, *args, **kwargs):
5052
super().__init__(*args, **kwargs)
5153
self.fields['phone'].widget.attrs.update(
5254
{
53-
'placeholder': '1234567890'
55+
'placeholder': _('1234567890')
5456
})
5557
self.fields['additional_info'].widget.attrs.update(
5658
{
@@ -61,26 +63,26 @@ def __init__(self, *args, **kwargs):
6163
{
6264
'rows': 2,
6365
'class': 'form-control',
64-
'placeholder': '1234 Main St, City, State, Zip Code',
66+
'placeholder': _('1234 Main St, City, State, Zip Code'),
6567
'required': 'true'
6668
})
6769
self.fields['additional_info'].widget.attrs.update(
6870
{
6971
'class': 'form-control',
70-
'placeholder': 'I would like to be contacted by phone.'
72+
'placeholder': _('I would like to be contacted by phone.')
7173
})
7274

7375

7476
class ClientDataForm(forms.Form):
75-
name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control'}))
76-
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control'}))
77+
name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('John Doe')}))
78+
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': _('[email protected]')}))
7779

7880

7981
class PersonalInformationForm(forms.Form):
8082
# first_name, last_name, email
81-
first_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control'}))
82-
last_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control'}))
83-
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control'}))
83+
first_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('John')}))
84+
last_name = forms.CharField(max_length=50, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Doe')}))
85+
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': _('[email protected]')}))
8486

8587
def __init__(self, *args, **kwargs):
8688
self.user = kwargs.pop('user', None) # pop the user from the kwargs
@@ -205,19 +207,19 @@ class Meta:
205207
}),
206208
'description': forms.Textarea(attrs={
207209
'class': 'form-control',
208-
'placeholder': "Example: Overview of client's needs."
210+
'placeholder': _("Example: Overview of client's needs.")
209211
}),
210212
'duration': forms.TextInput(attrs={
211213
'class': 'form-control',
212-
'placeholder': 'HH:MM:SS, (example: 00:15:00 for 15 minutes)'
214+
'placeholder': _('HH:MM:SS, (example: 00:15:00 for 15 minutes)')
213215
}),
214216
'price': forms.NumberInput(attrs={
215217
'class': 'form-control',
216-
'placeholder': 'Example: 100.00 (0 for free)'
218+
'placeholder': _('Example: 100.00 (0 for free)')
217219
}),
218220
'down_payment': forms.NumberInput(attrs={
219221
'class': 'form-control',
220-
'placeholder': 'Example: 50.00 (0 for free)'
222+
'placeholder': _('Example: 50.00 (0 for free)')
221223
}),
222224
'image': forms.ClearableFileInput(attrs={'class': 'form-control'}),
223225
'currency': forms.Select(choices=[('USD', 'USD'), ('EUR', 'EUR'), ('GBP', 'GBP')],

0 commit comments

Comments
 (0)