Write your representatives, EU version

Compare changes

Choose any two refs to compare.

+3647 -420
+3
.gitignore
··· 14 14 *.sqlite3 15 15 db.sqlite3 16 16 17 + # Compiled translation files (generated from .po) 18 + *.mo 19 + 17 20 # Shapefile components (use fetch_wahlkreis_data to regenerate GeoJSON) 18 21 *.shp 19 22 *.shx
+41
README.md
··· 7 7 4. Download constituency boundaries: `uv run python manage.py fetch_wahlkreis_data` (required for accurate address-based matching). 8 8 5. Launch the dev server with `uv run python manage.py runserver` and visit http://localhost:8000/. 9 9 10 + ## Internationalization 11 + 12 + WriteThem.eu supports German (default) and English. 13 + 14 + ### Using the Site 15 + 16 + - Visit `/de/` for German interface 17 + - Visit `/en/` for English interface 18 + - Use the language switcher in the header to toggle languages 19 + - Language preference is saved in a cookie 20 + 21 + ### For Developers 22 + 23 + **Translation workflow:** 24 + 25 + 1. Wrap new UI strings with translation functions: 26 + - Templates: `{% trans "Text" %}` or `{% blocktrans %}` 27 + - Python: `gettext()` or `gettext_lazy()` 28 + 29 + 2. Extract strings to .po files: 30 + ```bash 31 + cd website 32 + uv run python manage.py makemessages -l de -l en 33 + ``` 34 + 35 + 3. Translate strings in `.po` files: 36 + - Edit `locale/de/LC_MESSAGES/django.po` (German translations) 37 + - Edit `locale/en/LC_MESSAGES/django.po` (English, mostly identity translations) 38 + 39 + 4. Compile translations: 40 + ```bash 41 + uv run python manage.py compilemessages 42 + ``` 43 + 44 + 5. Check translation completeness: 45 + ```bash 46 + uv run python manage.py check_translations 47 + ``` 48 + 49 + **Important:** All code, comments, and translation keys should be in English. Only .po files contain actual translations. 50 + 10 51 ## Architecture 11 52 - **Frameworks**: Django 5.2 / Python 3.13 managed with `uv`. The project root holds dependency metadata; all Django code lives in `website/` (settings in `writethem/`, app logic in `letters/`). 12 53 - **Domain models**: `letters/models.py` defines parliaments, terms, constituencies, representatives, committees, letters, signatures, identity verification, and moderation reports. Relationships reflect multi-level mandates (EU/Federal/State) and committee membership.
+9
docs/matching-algorithm.md
··· 81 81 python manage.py test letters.tests.test_topic_mapping 82 82 python manage.py test letters.tests.test_constituency_suggestions 83 83 ``` 84 + 85 + ## Internationalization 86 + 87 + The constituency matching system works identically in both German and English: 88 + 89 + - Addresses can be entered in German format (standard use case) 90 + - UI language (German/English) does not affect geocoding or matching logic 91 + - Representative names, constituency names, and geographic data remain in original German 92 + - All user-facing labels and messages are translated
+1127
docs/plans/2025-10-14-i18n-implementation.md
··· 1 + # German + English Internationalization Implementation Plan 2 + 3 + > **For Claude:** Use `${SUPERPOWERS_SKILLS_ROOT}/skills/collaboration/executing-plans/SKILL.md` to implement this plan task-by-task. 4 + 5 + **Goal:** Implement full bilingual support (German + English) using Django's built-in i18n system. 6 + 7 + **Architecture:** Configure Django i18n settings, wrap all UI strings in gettext translation functions, create German and English locale files (.po), add language switcher component, and configure URL patterns with language prefixes. 8 + 9 + **Tech Stack:** Django 5.2 i18n framework, gettext, .po/.mo translation files, LocaleMiddleware 10 + 11 + --- 12 + 13 + ## Task 1: Configure Django i18n Settings 14 + 15 + **Files:** 16 + - Modify: `website/writethem/settings.py:104-146` 17 + 18 + **Step 1: Write the failing test** 19 + 20 + Create: `website/letters/tests/test_i18n.py` 21 + 22 + ```python 23 + # ABOUTME: Tests for internationalization configuration and functionality. 24 + # ABOUTME: Verifies language switching, URL prefixes, and translation completeness. 25 + 26 + from django.test import TestCase 27 + from django.conf import settings 28 + 29 + 30 + class I18nConfigurationTests(TestCase): 31 + def test_i18n_enabled(self): 32 + """Test that USE_I18N is enabled.""" 33 + self.assertTrue(settings.USE_I18N) 34 + 35 + def test_supported_languages(self): 36 + """Test that German and English are configured.""" 37 + language_codes = [code for code, name in settings.LANGUAGES] 38 + self.assertIn('de', language_codes) 39 + self.assertIn('en', language_codes) 40 + 41 + def test_locale_paths_configured(self): 42 + """Test that LOCALE_PATHS is set.""" 43 + self.assertTrue(len(settings.LOCALE_PATHS) > 0) 44 + ``` 45 + 46 + **Step 2: Run test to verify it fails** 47 + 48 + Run: `uv run python manage.py test letters.tests.test_i18n::I18nConfigurationTests -v` 49 + Expected: FAIL with assertion errors (USE_I18N=False, LANGUAGES not configured, LOCALE_PATHS not set) 50 + 51 + **Step 3: Update settings.py** 52 + 53 + In `website/writethem/settings.py`, replace lines 104-114: 54 + 55 + ```python 56 + # Internationalization 57 + # https://docs.djangoproject.com/en/5.2/topics/i18n/ 58 + 59 + LANGUAGE_CODE = 'de' 60 + LANGUAGES = [ 61 + ('de', 'Deutsch'), 62 + ('en', 'English'), 63 + ] 64 + 65 + TIME_ZONE = 'Europe/Berlin' 66 + 67 + USE_I18N = True 68 + USE_L10N = True 69 + 70 + USE_TZ = True 71 + 72 + # Locale paths - where Django looks for .po files 73 + LOCALE_PATHS = [ 74 + BASE_DIR / 'locale', 75 + ] 76 + ``` 77 + 78 + **Step 4: Add LocaleMiddleware** 79 + 80 + In `website/writethem/settings.py`, modify MIDDLEWARE list (lines 43-51): 81 + 82 + ```python 83 + MIDDLEWARE = [ 84 + 'django.middleware.security.SecurityMiddleware', 85 + 'django.contrib.sessions.middleware.SessionMiddleware', 86 + 'django.middleware.locale.LocaleMiddleware', # NEW - handles language detection 87 + 'django.middleware.common.CommonMiddleware', 88 + 'django.middleware.csrf.CsrfViewMiddleware', 89 + 'django.contrib.auth.middleware.AuthenticationMiddleware', 90 + 'django.contrib.messages.middleware.MessageMiddleware', 91 + 'django.middleware.clickjacking.XFrameOptionsMiddleware', 92 + ] 93 + ``` 94 + 95 + **Step 5: Run test to verify it passes** 96 + 97 + Run: `uv run python manage.py test letters.tests.test_i18n::I18nConfigurationTests -v` 98 + Expected: PASS (3 tests) 99 + 100 + **Step 6: Commit** 101 + 102 + ```bash 103 + git add website/writethem/settings.py website/letters/tests/test_i18n.py 104 + git commit -m "feat: configure Django i18n with German and English support" 105 + ``` 106 + 107 + --- 108 + 109 + ## Task 2: Configure URL Patterns with Language Prefixes 110 + 111 + **Files:** 112 + - Modify: `website/writethem/urls.py` 113 + 114 + **Step 1: Write the failing test** 115 + 116 + Add to `website/letters/tests/test_i18n.py`: 117 + 118 + ```python 119 + class I18nURLTests(TestCase): 120 + def test_german_url_prefix_works(self): 121 + """Test that German URL prefix is accessible.""" 122 + response = self.client.get('/de/') 123 + self.assertEqual(response.status_code, 200) 124 + 125 + def test_english_url_prefix_works(self): 126 + """Test that English URL prefix is accessible.""" 127 + response = self.client.get('/en/') 128 + self.assertEqual(response.status_code, 200) 129 + 130 + def test_set_language_endpoint_exists(self): 131 + """Test that language switcher endpoint exists.""" 132 + from django.urls import reverse 133 + url = reverse('set_language') 134 + self.assertEqual(url, '/i18n/setlang/') 135 + ``` 136 + 137 + **Step 2: Run test to verify it fails** 138 + 139 + Run: `uv run python manage.py test letters.tests.test_i18n::I18nURLTests -v` 140 + Expected: FAIL (URLs not configured with language prefixes) 141 + 142 + **Step 3: Update URLs configuration** 143 + 144 + Replace entire contents of `website/writethem/urls.py`: 145 + 146 + ```python 147 + """ 148 + URL configuration for writethem project. 149 + 150 + The `urlpatterns` list routes URLs to views. For more information please see: 151 + https://docs.djangoproject.com/en/5.2/topics/http/urls/ 152 + Examples: 153 + Function views 154 + 1. Add an import: from my_app import views 155 + 2. Add a URL to urlpatterns: path('', views.home, name='home') 156 + Class-based views 157 + 1. Add an import: from other_app.views import Home 158 + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 159 + Including another URLconf 160 + 1. Import the include() function: from django.urls import include, path 161 + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 162 + """ 163 + from django.contrib import admin 164 + from django.urls import path, include 165 + from django.conf import settings 166 + from django.conf.urls.static import static 167 + from django.conf.urls.i18n import i18n_patterns 168 + from django.views.i18n import set_language 169 + 170 + urlpatterns = [ 171 + # Language switcher endpoint (no prefix) 172 + path('i18n/setlang/', set_language, name='set_language'), 173 + ] 174 + 175 + # All user-facing URLs get language prefix 176 + urlpatterns += i18n_patterns( 177 + path('admin/', admin.site.urls), 178 + path('', include('letters.urls')), 179 + prefix_default_language=True, 180 + ) 181 + 182 + if settings.DEBUG: 183 + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 184 + ``` 185 + 186 + **Step 4: Run test to verify it passes** 187 + 188 + Run: `uv run python manage.py test letters.tests.test_i18n::I18nURLTests -v` 189 + Expected: PASS (3 tests) 190 + 191 + **Step 5: Commit** 192 + 193 + ```bash 194 + git add website/writethem/urls.py 195 + git commit -m "feat: add i18n URL patterns with language prefixes" 196 + ``` 197 + 198 + --- 199 + 200 + ## Task 3: Create Locale Directory Structure 201 + 202 + **Files:** 203 + - Create: `website/locale/` directory structure 204 + 205 + **Step 1: Create directory structure** 206 + 207 + Run: 208 + ```bash 209 + cd website 210 + mkdir -p locale/de/LC_MESSAGES 211 + mkdir -p locale/en/LC_MESSAGES 212 + ``` 213 + 214 + **Step 2: Verify directories exist** 215 + 216 + Run: `ls -la locale/` 217 + Expected: Shows `de/` and `en/` directories 218 + 219 + **Step 3: Create .gitkeep files** 220 + 221 + Run: 222 + ```bash 223 + touch locale/de/LC_MESSAGES/.gitkeep 224 + touch locale/en/LC_MESSAGES/.gitkeep 225 + ``` 226 + 227 + This ensures git tracks the directory structure even before .po files are created. 228 + 229 + **Step 4: Commit** 230 + 231 + ```bash 232 + git add locale/ 233 + git commit -m "feat: create locale directory structure for translations" 234 + ``` 235 + 236 + --- 237 + 238 + ## Task 4: Wrap Base Template Strings 239 + 240 + **Files:** 241 + - Modify: `website/letters/templates/letters/base.html` 242 + 243 + **Step 1: Review current base template** 244 + 245 + Run: `cat website/letters/templates/letters/base.html` 246 + 247 + Identify all hardcoded strings that need translation. 248 + 249 + **Step 2: Add i18n load tag and wrap strings** 250 + 251 + At the top of `website/letters/templates/letters/base.html`, add after the first line: 252 + 253 + ```django 254 + {% load i18n %} 255 + ``` 256 + 257 + Then wrap all user-facing strings with `{% trans %}` tags. For example: 258 + 259 + - Navigation links: `{% trans "Home" %}`, `{% trans "Letters" %}`, `{% trans "Login" %}`, etc. 260 + - Button text: `{% trans "Sign Out" %}`, `{% trans "Sign In" %}`, etc. 261 + - Any other UI text 262 + 263 + **Important:** The exact changes depend on the current template content. Wrap every hardcoded user-facing string. 264 + 265 + **Step 3: Test template renders without errors** 266 + 267 + Run: `uv run python manage.py runserver` 268 + Visit: `http://localhost:8000/de/` 269 + Expected: Page loads without template errors (strings still in English because .po files don't exist yet) 270 + 271 + **Step 4: Commit** 272 + 273 + ```bash 274 + git add website/letters/templates/letters/base.html 275 + git commit -m "feat: wrap base template strings with i18n tags" 276 + ``` 277 + 278 + --- 279 + 280 + ## Task 5: Add Language Switcher Component 281 + 282 + **Files:** 283 + - Modify: `website/letters/templates/letters/base.html` 284 + 285 + **Step 1: Write the failing test** 286 + 287 + Add to `website/letters/tests/test_i18n.py`: 288 + 289 + ```python 290 + class LanguageSwitcherTests(TestCase): 291 + def test_language_switcher_present_in_page(self): 292 + """Test that language switcher form is present.""" 293 + response = self.client.get('/de/') 294 + self.assertContains(response, 'name="language"') 295 + self.assertContains(response, 'Deutsch') 296 + self.assertContains(response, 'English') 297 + 298 + def test_language_switch_changes_language(self): 299 + """Test that submitting language form changes language.""" 300 + response = self.client.post( 301 + '/i18n/setlang/', 302 + {'language': 'en', 'next': '/en/'}, 303 + follow=True 304 + ) 305 + self.assertEqual(response.status_code, 200) 306 + # Check cookie was set 307 + self.assertIn('django_language', response.cookies) 308 + ``` 309 + 310 + **Step 2: Run test to verify it fails** 311 + 312 + Run: `uv run python manage.py test letters.tests.test_i18n::LanguageSwitcherTests -v` 313 + Expected: FAIL (language switcher not present) 314 + 315 + **Step 3: Add language switcher to base template** 316 + 317 + In `website/letters/templates/letters/base.html`, add this component in an appropriate location (e.g., in the header/navigation area): 318 + 319 + ```django 320 + <div class="language-switcher"> 321 + <form action="{% url 'set_language' %}" method="post"> 322 + {% csrf_token %} 323 + <input name="next" type="hidden" value="{{ request.get_full_path }}"> 324 + <select name="language" onchange="this.form.submit()" aria-label="{% trans 'Select language' %}"> 325 + {% get_current_language as CURRENT_LANGUAGE %} 326 + {% get_available_languages as AVAILABLE_LANGUAGES %} 327 + {% for lang_code, lang_name in AVAILABLE_LANGUAGES %} 328 + <option value="{{ lang_code }}" {% if lang_code == CURRENT_LANGUAGE %}selected{% endif %}> 329 + {{ lang_name }} 330 + </option> 331 + {% endfor %} 332 + </select> 333 + </form> 334 + </div> 335 + ``` 336 + 337 + **Step 4: Run test to verify it passes** 338 + 339 + Run: `uv run python manage.py test letters.tests.test_i18n::LanguageSwitcherTests -v` 340 + Expected: PASS (2 tests) 341 + 342 + **Step 5: Commit** 343 + 344 + ```bash 345 + git add website/letters/templates/letters/base.html 346 + git commit -m "feat: add language switcher component to base template" 347 + ``` 348 + 349 + --- 350 + 351 + ## Task 6: Wrap Authentication Template Strings 352 + 353 + **Files:** 354 + - Modify: `website/letters/templates/registration/login.html` 355 + - Modify: `website/letters/templates/registration/register.html` 356 + - Modify: `website/letters/templates/registration/password_reset_form.html` 357 + - Modify: `website/letters/templates/registration/password_reset_done.html` 358 + - Modify: `website/letters/templates/registration/password_reset_confirm.html` 359 + - Modify: `website/letters/templates/registration/password_reset_complete.html` 360 + 361 + **Step 1: Add i18n load tag to each template** 362 + 363 + For each template file listed above, add at the top (after `{% extends %}`): 364 + 365 + ```django 366 + {% load i18n %} 367 + ``` 368 + 369 + **Step 2: Wrap all strings with trans tags** 370 + 371 + For each template, wrap user-facing strings: 372 + - Headings: `<h1>{% trans "Login" %}</h1>` 373 + - Labels: `{% trans "Email" %}`, `{% trans "Password" %}` 374 + - Buttons: `{% trans "Sign In" %}`, `{% trans "Register" %}`, `{% trans "Reset Password" %}` 375 + - Messages: `{% trans "Forgot your password?" %}`, etc. 376 + 377 + **Step 3: Test templates render** 378 + 379 + Run: `uv run python manage.py runserver` 380 + Visit each auth page: 381 + - `/de/login/` 382 + - `/de/register/` 383 + - `/de/password-reset/` 384 + 385 + Expected: Pages load without errors 386 + 387 + **Step 4: Commit** 388 + 389 + ```bash 390 + git add website/letters/templates/registration/ 391 + git commit -m "feat: wrap authentication template strings with i18n tags" 392 + ``` 393 + 394 + --- 395 + 396 + ## Task 7: Wrap Letter List and Detail Template Strings 397 + 398 + **Files:** 399 + - Modify: `website/letters/templates/letters/letter_list.html` 400 + - Modify: `website/letters/templates/letters/letter_detail.html` 401 + 402 + **Step 1: Add i18n load tag** 403 + 404 + Add to both templates after `{% extends %}`: 405 + 406 + ```django 407 + {% load i18n %} 408 + ``` 409 + 410 + **Step 2: Wrap strings in letter_list.html** 411 + 412 + Wrap all user-facing strings: 413 + - Headings: `{% trans "Open Letters" %}` 414 + - Buttons: `{% trans "Write Letter" %}`, `{% trans "Filter" %}`, `{% trans "Sort" %}` 415 + - Labels: `{% trans "Topic" %}`, `{% trans "Signatures" %}` 416 + - Empty states: `{% trans "No letters found" %}` 417 + 418 + For pluralization (e.g., signature counts), use `{% blocktrans %}`: 419 + 420 + ```django 421 + {% blocktrans count counter=letter.signatures.count %} 422 + {{ counter }} signature 423 + {% plural %} 424 + {{ counter }} signatures 425 + {% endblocktrans %} 426 + ``` 427 + 428 + **Step 3: Wrap strings in letter_detail.html** 429 + 430 + Wrap all strings: 431 + - Buttons: `{% trans "Sign Letter" %}`, `{% trans "Remove Signature" %}`, `{% trans "Share" %}`, `{% trans "Report" %}` 432 + - Labels: `{% trans "Recipient" %}`, `{% trans "Published" %}`, `{% trans "Signatures" %}` 433 + - Messages: `{% trans "You have signed this letter" %}` 434 + 435 + **Step 4: Test templates render** 436 + 437 + Run: `uv run python manage.py runserver` 438 + Visit: `/de/letters/` and any letter detail page 439 + Expected: Pages load without errors 440 + 441 + **Step 5: Commit** 442 + 443 + ```bash 444 + git add website/letters/templates/letters/letter_list.html website/letters/templates/letters/letter_detail.html 445 + git commit -m "feat: wrap letter list and detail template strings with i18n tags" 446 + ``` 447 + 448 + --- 449 + 450 + ## Task 8: Wrap Letter Creation Template Strings 451 + 452 + **Files:** 453 + - Modify: `website/letters/templates/letters/letter_form.html` 454 + 455 + **Step 1: Add i18n load tag** 456 + 457 + ```django 458 + {% load i18n %} 459 + ``` 460 + 461 + **Step 2: Wrap all strings** 462 + 463 + Wrap: 464 + - Headings: `{% trans "Write an Open Letter" %}` 465 + - Form labels: `{% trans "Title" %}`, `{% trans "Content" %}`, `{% trans "Recipient" %}` 466 + - Help text: `{% trans "Minimum 500 characters" %}` 467 + - Warnings: `{% trans "Once published, letters cannot be edited" %}` 468 + - Buttons: `{% trans "Publish Letter" %}`, `{% trans "Preview" %}`, `{% trans "Cancel" %}` 469 + 470 + **Step 3: Update form class with verbose_name** 471 + 472 + Modify: `website/letters/forms.py` 473 + 474 + Add at the top: 475 + ```python 476 + from django.utils.translation import gettext_lazy as _ 477 + ``` 478 + 479 + For each form field, add `label` parameter: 480 + ```python 481 + title = forms.CharField( 482 + label=_("Title"), 483 + max_length=200, 484 + help_text=_("A clear, concise title for your letter") 485 + ) 486 + ``` 487 + 488 + **Step 4: Test template renders** 489 + 490 + Visit: `/de/letters/new/` 491 + Expected: Page loads without errors 492 + 493 + **Step 5: Commit** 494 + 495 + ```bash 496 + git add website/letters/templates/letters/letter_form.html website/letters/forms.py 497 + git commit -m "feat: wrap letter creation template and form strings with i18n" 498 + ``` 499 + 500 + --- 501 + 502 + ## Task 9: Wrap Profile and Account Template Strings 503 + 504 + **Files:** 505 + - Modify: `website/letters/templates/letters/profile.html` 506 + - Modify: `website/letters/templates/letters/account_delete.html` 507 + - Modify: Any other account-related templates 508 + 509 + **Step 1: Add i18n load tag to each template** 510 + 511 + ```django 512 + {% load i18n %} 513 + ``` 514 + 515 + **Step 2: Wrap all strings** 516 + 517 + Profile page: 518 + - Headings: `{% trans "Your Profile" %}`, `{% trans "Authored Letters" %}`, `{% trans "Signed Letters" %}` 519 + - Buttons: `{% trans "Edit Profile" %}`, `{% trans "Delete Account" %}` 520 + - Labels: `{% trans "Email" %}`, `{% trans "Verified" %}`, `{% trans "Unverified" %}` 521 + 522 + Account deletion: 523 + - Warnings: `{% trans "This action cannot be undone" %}` 524 + - Buttons: `{% trans "Confirm Deletion" %}`, `{% trans "Cancel" %}` 525 + 526 + **Step 3: Test templates render** 527 + 528 + Visit profile and account pages 529 + Expected: Pages load without errors 530 + 531 + **Step 4: Commit** 532 + 533 + ```bash 534 + git add website/letters/templates/letters/profile.html website/letters/templates/letters/account_delete.html 535 + git commit -m "feat: wrap profile and account template strings with i18n tags" 536 + ``` 537 + 538 + --- 539 + 540 + ## Task 10: Extract Translation Strings to .po Files 541 + 542 + **Files:** 543 + - Create: `website/locale/de/LC_MESSAGES/django.po` 544 + - Create: `website/locale/en/LC_MESSAGES/django.po` 545 + 546 + **Step 1: Run makemessages for German** 547 + 548 + Run: 549 + ```bash 550 + cd website 551 + uv run python manage.py makemessages -l de 552 + ``` 553 + 554 + Expected: Creates/updates `locale/de/LC_MESSAGES/django.po` with all translatable strings 555 + 556 + **Step 2: Run makemessages for English** 557 + 558 + Run: 559 + ```bash 560 + uv run python manage.py makemessages -l en 561 + ``` 562 + 563 + Expected: Creates/updates `locale/en/LC_MESSAGES/django.po` 564 + 565 + **Step 3: Verify .po files created** 566 + 567 + Run: 568 + ```bash 569 + ls -la locale/de/LC_MESSAGES/ 570 + ls -la locale/en/LC_MESSAGES/ 571 + ``` 572 + 573 + Expected: Both show `django.po` files 574 + 575 + **Step 4: Check .po file contents** 576 + 577 + Run: 578 + ```bash 579 + head -n 30 locale/de/LC_MESSAGES/django.po 580 + ``` 581 + 582 + Expected: Shows header and first few msgid/msgstr pairs 583 + 584 + **Step 5: Commit** 585 + 586 + ```bash 587 + git add locale/ 588 + git commit -m "feat: extract translatable strings to .po files" 589 + ``` 590 + 591 + --- 592 + 593 + ## Task 11: Translate German Strings in .po File 594 + 595 + **Files:** 596 + - Modify: `website/locale/de/LC_MESSAGES/django.po` 597 + 598 + **Step 1: Open German .po file** 599 + 600 + Open `website/locale/de/LC_MESSAGES/django.po` for editing 601 + 602 + **Step 2: Translate strings systematically** 603 + 604 + Go through each `msgid` and add German translation to `msgstr`: 605 + 606 + ```po 607 + #: letters/templates/letters/base.html:10 608 + msgid "Home" 609 + msgstr "Startseite" 610 + 611 + #: letters/templates/letters/base.html:11 612 + msgid "Letters" 613 + msgstr "Briefe" 614 + 615 + #: letters/templates/letters/base.html:12 616 + msgid "Sign In" 617 + msgstr "Anmelden" 618 + 619 + #: letters/templates/letters/base.html:13 620 + msgid "Sign Out" 621 + msgstr "Abmelden" 622 + 623 + #: letters/templates/letters/letter_list.html:5 624 + msgid "Open Letters" 625 + msgstr "Offene Briefe" 626 + 627 + #: letters/templates/letters/letter_list.html:8 628 + msgid "Write Letter" 629 + msgstr "Brief Schreiben" 630 + 631 + #: letters/templates/letters/letter_detail.html:15 632 + msgid "Sign Letter" 633 + msgstr "Brief Unterschreiben" 634 + 635 + #: letters/templates/letters/letter_detail.html:18 636 + msgid "Remove Signature" 637 + msgstr "Unterschrift Entfernen" 638 + 639 + #: letters/templates/letters/letter_detail.html:21 640 + msgid "Share" 641 + msgstr "Teilen" 642 + 643 + #: letters/templates/letters/letter_form.html:5 644 + msgid "Write an Open Letter" 645 + msgstr "Einen Offenen Brief Schreiben" 646 + 647 + #: letters/templates/letters/letter_form.html:10 648 + msgid "Title" 649 + msgstr "Titel" 650 + 651 + #: letters/templates/letters/letter_form.html:11 652 + msgid "Content" 653 + msgstr "Inhalt" 654 + 655 + #: letters/templates/letters/letter_form.html:12 656 + msgid "Minimum 500 characters" 657 + msgstr "Mindestens 500 Zeichen" 658 + 659 + #: letters/templates/letters/letter_form.html:15 660 + msgid "Once published, letters cannot be edited" 661 + msgstr "Nach Verรถffentlichung kรถnnen Briefe nicht mehr bearbeitet werden" 662 + 663 + #: letters/templates/letters/letter_form.html:20 664 + msgid "Publish Letter" 665 + msgstr "Brief Verรถffentlichen" 666 + 667 + #: letters/templates/registration/login.html:5 668 + msgid "Login" 669 + msgstr "Anmeldung" 670 + 671 + #: letters/templates/registration/login.html:10 672 + msgid "Email" 673 + msgstr "E-Mail" 674 + 675 + #: letters/templates/registration/login.html:11 676 + msgid "Password" 677 + msgstr "Passwort" 678 + 679 + #: letters/templates/registration/login.html:15 680 + msgid "Forgot your password?" 681 + msgstr "Passwort vergessen?" 682 + 683 + #: letters/templates/registration/register.html:5 684 + msgid "Register" 685 + msgstr "Registrieren" 686 + ``` 687 + 688 + **Note:** The exact strings will depend on what was extracted in Task 10. Translate ALL msgid entries systematically. 689 + 690 + **Step 3: Save the file** 691 + 692 + Ensure all translations are complete (no empty `msgstr ""` entries) 693 + 694 + **Step 4: Commit** 695 + 696 + ```bash 697 + git add locale/de/LC_MESSAGES/django.po 698 + git commit -m "feat: add German translations to .po file" 699 + ``` 700 + 701 + --- 702 + 703 + ## Task 12: Populate English .po File 704 + 705 + **Files:** 706 + - Modify: `website/locale/en/LC_MESSAGES/django.po` 707 + 708 + **Step 1: Open English .po file** 709 + 710 + Open `website/locale/en/LC_MESSAGES/django.po` for editing 711 + 712 + **Step 2: Add identity translations** 713 + 714 + For English, most translations are identity (msgstr = msgid): 715 + 716 + ```po 717 + #: letters/templates/letters/base.html:10 718 + msgid "Home" 719 + msgstr "Home" 720 + 721 + #: letters/templates/letters/base.html:11 722 + msgid "Letters" 723 + msgstr "Letters" 724 + ``` 725 + 726 + Go through all entries and copy msgid to msgstr (they should be identical for English). 727 + 728 + **Step 3: Save the file** 729 + 730 + **Step 4: Commit** 731 + 732 + ```bash 733 + git add locale/en/LC_MESSAGES/django.po 734 + git commit -m "feat: add English identity translations to .po file" 735 + ``` 736 + 737 + --- 738 + 739 + ## Task 13: Compile Translation Files 740 + 741 + **Files:** 742 + - Create: `website/locale/de/LC_MESSAGES/django.mo` 743 + - Create: `website/locale/en/LC_MESSAGES/django.mo` 744 + 745 + **Step 1: Run compilemessages** 746 + 747 + Run: 748 + ```bash 749 + cd website 750 + uv run python manage.py compilemessages 751 + ``` 752 + 753 + Expected: Creates `django.mo` files for both German and English 754 + 755 + **Step 2: Verify .mo files created** 756 + 757 + Run: 758 + ```bash 759 + ls -la locale/de/LC_MESSAGES/ 760 + ls -la locale/en/LC_MESSAGES/ 761 + ``` 762 + 763 + Expected: Both show `django.mo` files (binary format) 764 + 765 + **Step 3: Test translations work** 766 + 767 + Run: `uv run python manage.py runserver` 768 + 769 + Visit `/de/` - should show German interface 770 + Visit `/en/` - should show English interface 771 + 772 + Use language switcher to toggle between languages. 773 + 774 + **Step 4: Add .mo files to .gitignore** 775 + 776 + Modify `.gitignore` in repository root, add: 777 + ``` 778 + # Compiled translation files (generated from .po) 779 + *.mo 780 + ``` 781 + 782 + **Note:** .mo files are generated artifacts and don't need to be tracked in git. 783 + 784 + **Step 5: Commit** 785 + 786 + ```bash 787 + git add .gitignore 788 + git commit -m "chore: add compiled translation files to .gitignore" 789 + ``` 790 + 791 + --- 792 + 793 + ## Task 14: Create Translation Completeness Check Command 794 + 795 + **Files:** 796 + - Create: `website/letters/management/commands/check_translations.py` 797 + 798 + **Step 1: Write the failing test** 799 + 800 + Add to `website/letters/tests/test_i18n.py`: 801 + 802 + ```python 803 + from django.core.management import call_command 804 + from io import StringIO 805 + 806 + 807 + class TranslationCompletenessTests(TestCase): 808 + def test_check_translations_command_exists(self): 809 + """Test that check_translations command can be called.""" 810 + out = StringIO() 811 + call_command('check_translations', stdout=out) 812 + output = out.getvalue() 813 + self.assertIn('Deutsch', output) 814 + self.assertIn('English', output) 815 + ``` 816 + 817 + **Step 2: Run test to verify it fails** 818 + 819 + Run: `uv run python manage.py test letters.tests.test_i18n::TranslationCompletenessTests -v` 820 + Expected: FAIL (command doesn't exist) 821 + 822 + **Step 3: Create management command** 823 + 824 + Create: `website/letters/management/commands/check_translations.py` 825 + 826 + ```python 827 + # ABOUTME: Management command to check translation completeness and report coverage. 828 + # ABOUTME: Analyzes .po files to find untranslated strings and calculate coverage percentage. 829 + 830 + from django.core.management.base import BaseCommand 831 + from django.conf import settings 832 + import pathlib 833 + 834 + 835 + class Command(BaseCommand): 836 + help = "Check translation completeness for all configured languages" 837 + 838 + def add_arguments(self, parser): 839 + parser.add_argument( 840 + '--language', 841 + type=str, 842 + help='Check specific language (e.g., "de" or "en")', 843 + ) 844 + 845 + def handle(self, *args, **options): 846 + locale_paths = settings.LOCALE_PATHS 847 + languages = settings.LANGUAGES 848 + 849 + target_language = options.get('language') 850 + 851 + if target_language: 852 + languages_to_check = [(target_language, None)] 853 + else: 854 + languages_to_check = languages 855 + 856 + for lang_code, lang_name in languages_to_check: 857 + self.check_language(locale_paths[0], lang_code, lang_name) 858 + 859 + def check_language(self, locale_path, lang_code, lang_name): 860 + """Check translation completeness for a single language.""" 861 + po_file = pathlib.Path(locale_path) / lang_code / 'LC_MESSAGES' / 'django.po' 862 + 863 + if not po_file.exists(): 864 + self.stdout.write(self.style.ERROR( 865 + f"\n{lang_code}: No .po file found at {po_file}" 866 + )) 867 + return 868 + 869 + total = 0 870 + translated = 0 871 + untranslated = [] 872 + 873 + with open(po_file, 'r', encoding='utf-8') as f: 874 + current_msgid = None 875 + for line in f: 876 + line = line.strip() 877 + if line.startswith('msgid "') and not line.startswith('msgid ""'): 878 + current_msgid = line[7:-1] # Extract string between quotes 879 + total += 1 880 + elif line.startswith('msgstr "'): 881 + msgstr = line[8:-1] 882 + if msgstr: # Non-empty translation 883 + translated += 1 884 + elif current_msgid: 885 + untranslated.append(current_msgid) 886 + current_msgid = None 887 + 888 + if total == 0: 889 + self.stdout.write(self.style.WARNING( 890 + f"\n{lang_code}: No translatable strings found" 891 + )) 892 + return 893 + 894 + coverage = (translated / total) * 100 895 + display_name = lang_name if lang_name else lang_code 896 + 897 + self.stdout.write(self.style.SUCCESS( 898 + f"\n{display_name} ({lang_code}):" 899 + )) 900 + self.stdout.write(f" Total strings: {total}") 901 + self.stdout.write(f" Translated: {translated}") 902 + self.stdout.write(f" Untranslated: {len(untranslated)}") 903 + self.stdout.write(f" Coverage: {coverage:.1f}%") 904 + 905 + if untranslated: 906 + self.stdout.write(self.style.WARNING( 907 + f"\nMissing translations ({len(untranslated)}):" 908 + )) 909 + for msgid in untranslated[:10]: # Show first 10 910 + self.stdout.write(f" - {msgid}") 911 + if len(untranslated) > 10: 912 + self.stdout.write(f" ... and {len(untranslated) - 10} more") 913 + else: 914 + self.stdout.write(self.style.SUCCESS( 915 + "\nAll strings translated!" 916 + )) 917 + ``` 918 + 919 + **Step 4: Run test to verify it passes** 920 + 921 + Run: `uv run python manage.py test letters.tests.test_i18n::TranslationCompletenessTests -v` 922 + Expected: PASS 923 + 924 + **Step 5: Test command manually** 925 + 926 + Run: 927 + ```bash 928 + uv run python manage.py check_translations 929 + ``` 930 + 931 + Expected: Shows coverage report for both German and English 932 + 933 + **Step 6: Commit** 934 + 935 + ```bash 936 + git add website/letters/management/commands/check_translations.py 937 + git commit -m "feat: add check_translations management command" 938 + ``` 939 + 940 + --- 941 + 942 + ## Task 15: Update Documentation 943 + 944 + **Files:** 945 + - Modify: `README.md` 946 + - Modify: `docs/matching-algorithm.md` (add i18n section) 947 + 948 + **Step 1: Update README with i18n information** 949 + 950 + Add a new section to `README.md`: 951 + 952 + ```markdown 953 + ## Internationalization 954 + 955 + WriteThem.eu supports German (default) and English. 956 + 957 + ### Using the Site 958 + 959 + - Visit `/de/` for German interface 960 + - Visit `/en/` for English interface 961 + - Use the language switcher in the header to toggle languages 962 + - Language preference is saved in a cookie 963 + 964 + ### For Developers 965 + 966 + **Translation workflow:** 967 + 968 + 1. Wrap new UI strings with translation functions: 969 + - Templates: `{% trans "Text" %}` or `{% blocktrans %}` 970 + - Python: `gettext()` or `gettext_lazy()` 971 + 972 + 2. Extract strings to .po files: 973 + ```bash 974 + cd website 975 + uv run python manage.py makemessages -l de -l en 976 + ``` 977 + 978 + 3. Translate strings in `.po` files: 979 + - Edit `locale/de/LC_MESSAGES/django.po` (German translations) 980 + - Edit `locale/en/LC_MESSAGES/django.po` (English, mostly identity translations) 981 + 982 + 4. Compile translations: 983 + ```bash 984 + uv run python manage.py compilemessages 985 + ``` 986 + 987 + 5. Check translation completeness: 988 + ```bash 989 + uv run python manage.py check_translations 990 + ``` 991 + 992 + **Important:** All code, comments, and translation keys should be in English. Only .po files contain actual translations. 993 + ``` 994 + 995 + **Step 2: Add i18n section to matching-algorithm.md** 996 + 997 + Add at the end of `docs/matching-algorithm.md`: 998 + 999 + ```markdown 1000 + ## Internationalization 1001 + 1002 + The constituency matching system works identically in both German and English: 1003 + 1004 + - Addresses can be entered in German format (standard use case) 1005 + - UI language (German/English) does not affect geocoding or matching logic 1006 + - Representative names, constituency names, and geographic data remain in original German 1007 + - All user-facing labels and messages are translated 1008 + ``` 1009 + 1010 + **Step 3: Commit** 1011 + 1012 + ```bash 1013 + git add README.md docs/matching-algorithm.md 1014 + git commit -m "docs: add internationalization documentation" 1015 + ``` 1016 + 1017 + --- 1018 + 1019 + ## Task 16: Run Full Test Suite and Verify 1020 + 1021 + **Step 1: Run all tests** 1022 + 1023 + Run: 1024 + ```bash 1025 + cd website 1026 + uv run python manage.py test letters.tests.test_i18n letters.tests.test_address_matching letters.tests.test_topic_mapping letters.tests.test_constituency_suggestions 1027 + ``` 1028 + 1029 + Expected: All tests pass (check total count) 1030 + 1031 + **Step 2: Check translation completeness** 1032 + 1033 + Run: 1034 + ```bash 1035 + uv run python manage.py check_translations 1036 + ``` 1037 + 1038 + Expected: 100% coverage for both languages (or report any missing translations) 1039 + 1040 + **Step 3: Manual verification checklist** 1041 + 1042 + Run: `uv run python manage.py runserver` 1043 + 1044 + Test each page in both languages: 1045 + 1046 + **German (`/de/`):** 1047 + - [ ] Homepage loads in German 1048 + - [ ] Login page in German 1049 + - [ ] Register page in German 1050 + - [ ] Letter list in German 1051 + - [ ] Letter detail in German 1052 + - [ ] Letter creation form in German 1053 + - [ ] Profile page in German 1054 + - [ ] Language switcher works (toggles to English) 1055 + 1056 + **English (`/en/`):** 1057 + - [ ] Homepage loads in English 1058 + - [ ] Login page in English 1059 + - [ ] Register page in English 1060 + - [ ] Letter list in English 1061 + - [ ] Letter detail in English 1062 + - [ ] Letter creation form in English 1063 + - [ ] Profile page in English 1064 + - [ ] Language switcher works (toggles to German) 1065 + 1066 + **Step 4: Check for untranslated strings** 1067 + 1068 + While testing, look for any English text appearing on German pages (or vice versa). These indicate missed translations. 1069 + 1070 + If found, add them to .po files, compile, and test again. 1071 + 1072 + **Step 5: Create summary commit** 1073 + 1074 + ```bash 1075 + git add . 1076 + git commit -m "test: verify i18n implementation with full test suite" 1077 + ``` 1078 + 1079 + --- 1080 + 1081 + ## Verification Checklist 1082 + 1083 + Before merging this feature: 1084 + 1085 + - [ ] USE_I18N=True in settings 1086 + - [ ] LANGUAGES configured with German and English 1087 + - [ ] LOCALE_PATHS configured 1088 + - [ ] LocaleMiddleware added to MIDDLEWARE 1089 + - [ ] URL patterns use i18n_patterns() 1090 + - [ ] Language switcher present in base template 1091 + - [ ] All templates have `{% load i18n %}` 1092 + - [ ] All UI strings wrapped with `{% trans %}` or `{% blocktrans %}` 1093 + - [ ] German .po file fully translated (100% coverage) 1094 + - [ ] English .po file complete (identity translations) 1095 + - [ ] .mo files compile without errors 1096 + - [ ] check_translations command works 1097 + - [ ] All automated tests pass 1098 + - [ ] Manual testing in both languages successful 1099 + - [ ] Documentation updated 1100 + - [ ] No untranslated strings visible in UI 1101 + 1102 + --- 1103 + 1104 + ## Notes for Implementation 1105 + 1106 + **Language policy:** 1107 + - All code (variables, functions, classes): English 1108 + - All comments and docstrings: English 1109 + - All translation keys (msgid in .po): English 1110 + - .po files contain actual translations 1111 + 1112 + **Testing strategy:** 1113 + - TDD throughout: write test โ†’ verify fail โ†’ implement โ†’ verify pass โ†’ commit 1114 + - Run `uv run python manage.py test` frequently 1115 + - Use `uv run python manage.py runserver` for manual verification 1116 + - Use `check_translations` command to catch missing translations 1117 + 1118 + **Common pitfalls:** 1119 + - Forgetting `{% load i18n %}` at top of templates 1120 + - Not using `gettext_lazy` in models/forms (use lazy version for class-level strings) 1121 + - Mixing `{% trans %}` and `{% blocktrans %}` incorrectly (use blocktrans for variables) 1122 + - Not recompiling after editing .po files (run compilemessages) 1123 + 1124 + **Skills to reference:** 1125 + - @skills/testing/test-driven-development for TDD workflow 1126 + - @skills/debugging/systematic-debugging if tests fail unexpectedly 1127 + - @skills/collaboration/finishing-a-development-branch when merging back to feat/matching
+90
website/letters/management/commands/check_translations.py
··· 1 + # ABOUTME: Management command to check translation completeness and report coverage. 2 + # ABOUTME: Analyzes .po files to find untranslated strings and calculate coverage percentage. 3 + 4 + from django.core.management.base import BaseCommand 5 + from django.conf import settings 6 + import pathlib 7 + 8 + 9 + class Command(BaseCommand): 10 + help = "Check translation completeness for all configured languages" 11 + 12 + def add_arguments(self, parser): 13 + parser.add_argument( 14 + '--language', 15 + type=str, 16 + help='Check specific language (e.g., "de" or "en")', 17 + ) 18 + 19 + def handle(self, *args, **options): 20 + locale_paths = settings.LOCALE_PATHS 21 + languages = settings.LANGUAGES 22 + 23 + target_language = options.get('language') 24 + 25 + if target_language: 26 + languages_to_check = [(target_language, None)] 27 + else: 28 + languages_to_check = languages 29 + 30 + for lang_code, lang_name in languages_to_check: 31 + self.check_language(locale_paths[0], lang_code, lang_name) 32 + 33 + def check_language(self, locale_path, lang_code, lang_name): 34 + """Check translation completeness for a single language.""" 35 + po_file = pathlib.Path(locale_path) / lang_code / 'LC_MESSAGES' / 'django.po' 36 + 37 + if not po_file.exists(): 38 + self.stdout.write(self.style.ERROR( 39 + f"\n{lang_code}: No .po file found at {po_file}" 40 + )) 41 + return 42 + 43 + total = 0 44 + translated = 0 45 + untranslated = [] 46 + 47 + with open(po_file, 'r', encoding='utf-8') as f: 48 + current_msgid = None 49 + for line in f: 50 + line = line.strip() 51 + if line.startswith('msgid "') and not line.startswith('msgid ""'): 52 + current_msgid = line[7:-1] # Extract string between quotes 53 + total += 1 54 + elif line.startswith('msgstr "'): 55 + msgstr = line[8:-1] 56 + if msgstr: # Non-empty translation 57 + translated += 1 58 + elif current_msgid: 59 + untranslated.append(current_msgid) 60 + current_msgid = None 61 + 62 + if total == 0: 63 + self.stdout.write(self.style.WARNING( 64 + f"\n{lang_code}: No translatable strings found" 65 + )) 66 + return 67 + 68 + coverage = (translated / total) * 100 69 + display_name = lang_name if lang_name else lang_code 70 + 71 + self.stdout.write(self.style.SUCCESS( 72 + f"\n{display_name} ({lang_code}):" 73 + )) 74 + self.stdout.write(f" Total strings: {total}") 75 + self.stdout.write(f" Translated: {translated}") 76 + self.stdout.write(f" Untranslated: {len(untranslated)}") 77 + self.stdout.write(f" Coverage: {coverage:.1f}%") 78 + 79 + if untranslated: 80 + self.stdout.write(self.style.WARNING( 81 + f"\nMissing translations ({len(untranslated)}):" 82 + )) 83 + for msgid in untranslated[:10]: # Show first 10 84 + self.stdout.write(f" - {msgid}") 85 + if len(untranslated) > 10: 86 + self.stdout.write(f" ... and {len(untranslated) - 10} more") 87 + else: 88 + self.stdout.write(self.style.SUCCESS( 89 + "\nAll strings translated!" 90 + ))
+174
website/letters/management/commands/db_snapshot.py
··· 1 + # ABOUTME: Management command to save and restore SQLite database snapshots. 2 + # ABOUTME: Enables quick database state preservation for testing and development. 3 + 4 + import shutil 5 + import pathlib 6 + from datetime import datetime 7 + 8 + from django.core.management.base import BaseCommand, CommandError 9 + from django.conf import settings 10 + from letters.models import Parliament, Constituency, Representative, Committee, TopicArea 11 + 12 + 13 + class Command(BaseCommand): 14 + help = "Save or restore SQLite database snapshots to/from fixtures directory" 15 + 16 + def add_arguments(self, parser): 17 + subparsers = parser.add_subparsers( 18 + dest="subcommand", help="Available subcommands", title="subcommands" 19 + ) 20 + 21 + # Save subcommand 22 + save_parser = subparsers.add_parser( 23 + "save", help="Create a snapshot of the current database state" 24 + ) 25 + save_parser.add_argument( 26 + "description", 27 + type=str, 28 + help='Description of the snapshot (e.g., "with-representatives", "after-sync")', 29 + ) 30 + 31 + # Restore subcommand 32 + restore_parser = subparsers.add_parser( 33 + "restore", help="Restore database from a snapshot" 34 + ) 35 + restore_parser.add_argument( 36 + "snapshot_file", 37 + type=str, 38 + help='Snapshot filename (e.g., "db_snapshot_20241014_123456_with-representatives.sqlite3")', 39 + ) 40 + 41 + # List subcommand 42 + subparsers.add_parser("list", help="List available snapshots") 43 + 44 + def handle(self, *args, **options): 45 + subcommand = options.get("subcommand") 46 + 47 + if not subcommand: 48 + self.print_help("manage.py", "db_snapshot") 49 + return 50 + 51 + if subcommand == "save": 52 + self.handle_save(options) 53 + elif subcommand == "restore": 54 + self.handle_restore(options) 55 + elif subcommand == "list": 56 + self.handle_list(options) 57 + else: 58 + raise CommandError(f"Unknown subcommand: {subcommand}") 59 + 60 + def handle_save(self, options): 61 + """Create a timestamped snapshot of the current database""" 62 + description = options["description"] 63 + 64 + # Create filename with timestamp and description 65 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 66 + # Sanitize description for filename 67 + safe_description = "".join(c if c.isalnum() or c == "-" else "_" for c in description) 68 + snapshot_filename = f"db_snapshot_{timestamp}_{safe_description}.sqlite3" 69 + 70 + # Determine source database path 71 + db_path = pathlib.Path(settings.DATABASES["default"]["NAME"]) 72 + if not db_path.exists(): 73 + raise CommandError(f"Database file not found: {db_path}") 74 + 75 + # Create fixtures directory if it doesn't exist 76 + fixtures_dir = settings.BASE_DIR.parent / "fixtures" 77 + fixtures_dir.mkdir(exist_ok=True) 78 + 79 + snapshot_path = fixtures_dir / snapshot_filename 80 + 81 + self.stdout.write(self.style.SUCCESS("๐Ÿ“ธ Creating database snapshot...")) 82 + self.stdout.write(f" Source: {db_path}") 83 + self.stdout.write(f" Destination: {snapshot_path}") 84 + self.stdout.write(f" Description: {description}") 85 + 86 + # Copy the SQLite database file 87 + shutil.copy2(db_path, snapshot_path) 88 + 89 + self.stdout.write(self.style.SUCCESS("\nโœ… Snapshot created successfully!")) 90 + self.stdout.write(f" File: {snapshot_filename}") 91 + self.stdout.write(f" Size: {snapshot_path.stat().st_size / (1024 * 1024):.2f} MB") 92 + 93 + # Show current database stats 94 + self.stdout.write(self.style.SUCCESS("\n๐Ÿ“Š Snapshot contains:")) 95 + self.stdout.write(f" ๐Ÿ›๏ธ Parliaments: {Parliament.objects.count()}") 96 + self.stdout.write(f" ๐Ÿ“ Constituencies: {Constituency.objects.count()}") 97 + self.stdout.write(f" ๐Ÿ‘ค Representatives: {Representative.objects.count()}") 98 + self.stdout.write(f" ๐Ÿข Committees: {Committee.objects.count()}") 99 + self.stdout.write(f" ๐Ÿท๏ธ Topic Areas: {TopicArea.objects.count()}") 100 + 101 + def handle_restore(self, options): 102 + """Restore database from a snapshot""" 103 + snapshot_file = options["snapshot_file"] 104 + 105 + # Locate snapshot file 106 + fixtures_dir = settings.BASE_DIR.parent / "fixtures" 107 + snapshot_path = fixtures_dir / snapshot_file 108 + 109 + if not snapshot_path.exists(): 110 + raise CommandError(f"Snapshot file not found: {snapshot_path}") 111 + 112 + # Determine target database path 113 + db_path = pathlib.Path(settings.DATABASES["default"]["NAME"]) 114 + 115 + self.stdout.write(self.style.WARNING("โš ๏ธ Restoring database snapshot...")) 116 + self.stdout.write(f" Source: {snapshot_path}") 117 + self.stdout.write(f" Destination: {db_path}") 118 + self.stdout.write(f" Size: {snapshot_path.stat().st_size / (1024 * 1024):.2f} MB") 119 + 120 + # Confirm overwrite 121 + self.stdout.write( 122 + self.style.WARNING( 123 + "\nโš ๏ธ This will OVERWRITE your current database!" 124 + ) 125 + ) 126 + confirm = input("Type 'restore' to confirm: ") 127 + 128 + if confirm != "restore": 129 + self.stdout.write(self.style.ERROR("Restore cancelled.")) 130 + return 131 + 132 + # Backup current database before overwriting 133 + if db_path.exists(): 134 + backup_path = db_path.parent / f"{db_path.stem}_backup_before_restore{db_path.suffix}" 135 + self.stdout.write(f"\n๐Ÿ“ฆ Backing up current database to: {backup_path.name}") 136 + shutil.copy2(db_path, backup_path) 137 + 138 + # Restore the snapshot 139 + shutil.copy2(snapshot_path, db_path) 140 + 141 + self.stdout.write(self.style.SUCCESS("\nโœ… Database restored successfully!")) 142 + 143 + # Show restored database stats 144 + self.stdout.write(self.style.SUCCESS("\n๐Ÿ“Š Restored database contains:")) 145 + self.stdout.write(f" ๐Ÿ›๏ธ Parliaments: {Parliament.objects.count()}") 146 + self.stdout.write(f" ๐Ÿ“ Constituencies: {Constituency.objects.count()}") 147 + self.stdout.write(f" ๐Ÿ‘ค Representatives: {Representative.objects.count()}") 148 + self.stdout.write(f" ๐Ÿข Committees: {Committee.objects.count()}") 149 + self.stdout.write(f" ๐Ÿท๏ธ Topic Areas: {TopicArea.objects.count()}") 150 + 151 + def handle_list(self, options): 152 + """List available snapshots""" 153 + fixtures_dir = settings.BASE_DIR.parent / "fixtures" 154 + 155 + if not fixtures_dir.exists(): 156 + self.stdout.write(self.style.WARNING("No fixtures directory found.")) 157 + return 158 + 159 + # Find all snapshot files 160 + snapshots = sorted(fixtures_dir.glob("db_snapshot_*.sqlite3"), reverse=True) 161 + 162 + if not snapshots: 163 + self.stdout.write(self.style.WARNING("No snapshots found.")) 164 + return 165 + 166 + self.stdout.write(self.style.SUCCESS(f"\n๐Ÿ“ Available snapshots ({len(snapshots)}):")) 167 + self.stdout.write("") 168 + 169 + for snapshot in snapshots: 170 + size_mb = snapshot.stat().st_size / (1024 * 1024) 171 + mtime = datetime.fromtimestamp(snapshot.stat().st_mtime) 172 + self.stdout.write(f" โ€ข {snapshot.name}") 173 + self.stdout.write(f" Size: {size_mb:.2f} MB, Modified: {mtime.strftime('%Y-%m-%d %H:%M:%S')}") 174 + self.stdout.write("")
+27 -1
website/letters/templates/letters/base.html
··· 35 35 border-radius: 3px; 36 36 } 37 37 nav a:hover { background: #34495e; } 38 + .language-switcher { 39 + display: inline-block; 40 + margin-left: 1rem; 41 + } 42 + .language-switcher select { 43 + background: #34495e; 44 + color: white; 45 + border: 1px solid #4a6278; 46 + border-radius: 3px; 47 + padding: 0.3rem 0.6rem; 48 + cursor: pointer; 49 + } 50 + .language-switcher select:hover { 51 + background: #415a77; 52 + } 38 53 .messages { 39 54 list-style: none; 40 55 margin-bottom: 1rem; ··· 123 138 <h1>WriteThem.eu</h1> 124 139 <nav> 125 140 <a href="{% url 'letter_list' %}">{% trans "Letters" %}</a> 126 - <a href="{% url 'competency_overview' %}">Kompetenzen</a> 141 + <a href="{% url 'competency_overview' %}">{% trans "Competencies" %}</a> 127 142 {% if user.is_authenticated %} 128 143 <a href="{% url 'letter_create' %}">{% trans "Write Letter" %}</a> 129 144 <a href="{% url 'profile' %}">{% trans "Profile" %}</a> ··· 135 150 {% if user.is_staff %} 136 151 <a href="{% url 'admin:index' %}">{% trans "Admin" %}</a> 137 152 {% endif %} 153 + <div class="language-switcher"> 154 + <form action="{% url 'set_language' %}" method="post"> 155 + {% csrf_token %} 156 + <input name="next" type="hidden" value="{{ request.get_full_path }}"> 157 + <select name="language" onchange="this.form.submit()" aria-label="{% trans 'Select language' %}"> 158 + {% get_current_language as CURRENT_LANGUAGE %} 159 + <option value="de" {% if CURRENT_LANGUAGE == 'de' %}selected{% endif %}>Deutsch</option> 160 + <option value="en" {% if CURRENT_LANGUAGE == 'en' %}selected{% endif %}>English</option> 161 + </select> 162 + </form> 163 + </div> 138 164 </nav> 139 165 </div> 140 166 </header>
+2 -2
website/letters/templates/letters/letter_detail.html
··· 55 55 <div style="padding: 0.8rem 0; border-bottom: 1px solid #ecf0f1;"> 56 56 <strong>{{ signature.display_name }}</strong> 57 57 {% if signature.is_verified_constituent %} 58 - <span class="badge badge-verified">โœ“ Verified Constituent</span> 58 + <span class="badge badge-verified">{% trans "โœ“ Verified Constituent" %}</span> 59 59 {% elif signature.is_verified_non_constituent %} 60 - <span class="badge badge-verified">โœ“ Verified</span> 60 + <span class="badge badge-verified">{% trans "โœ“ Verified" %}</span> 61 61 {% endif %} 62 62 <p style="color: #7f8c8d; font-size: 0.85rem;">{{ signature.signed_at|date:"M d, Y" }}</p> 63 63 {% if signature.comment %}
+4 -4
website/letters/templates/letters/letter_list.html
··· 38 38 {% if is_paginated %} 39 39 <div style="text-align: center; margin: 2rem 0;"> 40 40 {% if page_obj.has_previous %} 41 - <a href="?page={{ page_obj.previous_page_number }}" class="btn btn-secondary">Previous</a> 41 + <a href="?page={{ page_obj.previous_page_number }}" class="btn btn-secondary">{% trans "Previous" %}</a> 42 42 {% endif %} 43 - <span style="margin: 0 1rem;">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span> 43 + <span style="margin: 0 1rem;">{% blocktrans with page=page_obj.number total=page_obj.paginator.num_pages %}Page {{ page }} of {{ total }}{% endblocktrans %}</span> 44 44 {% if page_obj.has_next %} 45 - <a href="?page={{ page_obj.next_page_number }}" class="btn btn-secondary">Next</a> 45 + <a href="?page={{ page_obj.next_page_number }}" class="btn btn-secondary">{% trans "Next" %}</a> 46 46 {% endif %} 47 47 </div> 48 48 {% endif %} 49 49 {% else %} 50 50 <div class="card"> 51 - <p>No letters found. <a href="{% url 'letter_create' %}">Be the first to write one!</a></p> 51 + <p>{% trans "No letters found." %} <a href="{% url 'letter_create' %}">{% trans "Be the first to write one!" %}</a></p> 52 52 </div> 53 53 {% endif %} 54 54 {% endblock %}
+89
website/letters/tests/test_i18n.py
··· 1 + # ABOUTME: Tests for internationalization configuration and functionality. 2 + # ABOUTME: Verifies language switching, URL prefixes, and translation completeness. 3 + 4 + from django.test import TestCase 5 + from django.conf import settings 6 + from django.core.management import call_command 7 + from io import StringIO 8 + 9 + 10 + class I18nConfigurationTests(TestCase): 11 + def test_i18n_enabled(self): 12 + """Test that USE_I18N is enabled.""" 13 + self.assertTrue(settings.USE_I18N) 14 + 15 + def test_supported_languages(self): 16 + """Test that German and English are configured.""" 17 + language_codes = [code for code, name in settings.LANGUAGES] 18 + self.assertIn('de', language_codes) 19 + self.assertIn('en', language_codes) 20 + 21 + def test_locale_paths_configured(self): 22 + """Test that LOCALE_PATHS is set.""" 23 + self.assertTrue(len(settings.LOCALE_PATHS) > 0) 24 + 25 + 26 + class I18nURLTests(TestCase): 27 + def test_german_url_prefix_works(self): 28 + """Test that German URL prefix is accessible.""" 29 + response = self.client.get('/de/') 30 + self.assertEqual(response.status_code, 200) 31 + 32 + def test_english_url_prefix_works(self): 33 + """Test that English URL prefix is accessible.""" 34 + response = self.client.get('/en/') 35 + self.assertEqual(response.status_code, 200) 36 + 37 + def test_set_language_endpoint_exists(self): 38 + """Test that language switcher endpoint exists.""" 39 + from django.urls import reverse 40 + url = reverse('set_language') 41 + self.assertEqual(url, '/i18n/setlang/') 42 + 43 + 44 + class LanguageSwitcherTests(TestCase): 45 + def test_language_switcher_present_in_page(self): 46 + """Test that language switcher form is present.""" 47 + response = self.client.get('/de/') 48 + self.assertContains(response, 'name="language"') 49 + self.assertContains(response, 'Deutsch') 50 + self.assertContains(response, 'English') 51 + 52 + def test_language_switch_changes_language(self): 53 + """Test that submitting language form changes language.""" 54 + response = self.client.post( 55 + '/i18n/setlang/', 56 + {'language': 'en', 'next': '/en/'}, 57 + ) 58 + # Check we got a redirect 59 + self.assertEqual(response.status_code, 302) 60 + # Check cookie was set 61 + self.assertIn('django_language', response.cookies) 62 + self.assertEqual(response.cookies['django_language'].value, 'en') 63 + 64 + 65 + class LetterFormI18nTests(TestCase): 66 + def test_letter_form_template_renders(self): 67 + """Test that letter creation form renders without errors.""" 68 + from django.contrib.auth.models import User 69 + # Create a user and log them in 70 + user = User.objects.create_user(username='testuser', password='testpass') 71 + self.client.login(username='testuser', password='testpass') 72 + 73 + # Test German version 74 + response = self.client.get('/de/letter/new/') 75 + self.assertEqual(response.status_code, 200) 76 + 77 + # Test English version 78 + response = self.client.get('/en/letter/new/') 79 + self.assertEqual(response.status_code, 200) 80 + 81 + 82 + class TranslationCompletenessTests(TestCase): 83 + def test_check_translations_command_exists(self): 84 + """Test that check_translations command can be called.""" 85 + out = StringIO() 86 + call_command('check_translations', stdout=out) 87 + output = out.getvalue() 88 + self.assertIn('Deutsch', output) 89 + self.assertIn('English', output)
website/locale/de/LC_MESSAGES/.gitkeep

This is a binary file and will not be displayed.

website/locale/de/LC_MESSAGES/django.mo

This is a binary file and will not be displayed.

+840 -409
website/locale/de/LC_MESSAGES/django.po
··· 6 6 msgstr "" 7 7 "Project-Id-Version: WriteThem.eu\n" 8 8 "Report-Msgid-Bugs-To: \n" 9 - "POT-Creation-Date: 2025-10-04 22:57+0200\n" 9 + "POT-Creation-Date: 2025-10-15 00:27+0200\n" 10 10 "PO-Revision-Date: 2025-01-10 12:00+0100\n" 11 11 "Last-Translator: \n" 12 12 "Language-Team: German\n" ··· 16 16 "Content-Transfer-Encoding: 8bit\n" 17 17 "Plural-Forms: nplurals=2; plural=(n != 1);\n" 18 18 19 - # Forms 20 - #: letters/forms.py:28 21 - msgid "Postal code (PLZ)" 22 - msgstr "Postleitzahl (PLZ)" 19 + #: letters/admin.py:75 20 + msgid "Mandate Details" 21 + msgstr "Mandatsdetails" 23 22 24 - #: letters/forms.py:29 25 - #, fuzzy 26 - #| msgid "Use your PLZ to narrow down representatives from your constituency." 27 - msgid "Use your PLZ to narrow down representatives from your parliament." 28 - msgstr "" 29 - "Verwenden Sie Ihre PLZ, um Abgeordnete aus Ihrem Wahlkreis einzugrenzen." 23 + #: letters/admin.py:78 24 + msgid "Focus Areas" 25 + msgstr "Schwerpunktbereiche" 30 26 31 - #: letters/forms.py:30 32 - msgid "e.g. 10115" 33 - msgstr "z.B. 10115" 27 + #: letters/admin.py:81 letters/admin.py:95 28 + msgid "Photo" 29 + msgstr "Foto" 34 30 35 - #: letters/forms.py:35 36 - msgid "Comma-separated tags (e.g., \"climate, transport, education\")" 37 - msgstr "Komma-getrennte Schlagwรถrter (z.B. \"Klima, Verkehr, Bildung\")" 31 + #: letters/admin.py:85 32 + msgid "Metadata" 33 + msgstr "Metadaten" 38 34 39 - #: letters/forms.py:36 40 - msgid "climate, transport, education" 41 - msgstr "Klima, Verkehr, Bildung" 35 + #: letters/admin.py:94 36 + msgid "No photo" 37 + msgstr "Kein Foto" 38 + 39 + #: letters/admin.py:167 40 + msgid "Topic Areas" 41 + msgstr "Themenbereiche" 42 + 43 + #: letters/forms.py:25 44 + msgid "" 45 + "An account with this email already exists. If you registered before, please " 46 + "check your inbox for the activation link or reset your password." 47 + msgstr "" 48 + "Ein Konto mit dieser E-Mail-Adresse existiert bereits. Falls Sie sich zuvor " 49 + "registriert haben, prรผfen Sie Ihren Posteingang auf den Aktivierungslink oder " 50 + "setzen Sie Ihr Passwort zurรผck." 42 51 43 - #: letters/forms.py:43 52 + #: letters/forms.py:37 44 53 msgid "Title" 45 54 msgstr "Titel" 46 55 47 - #: letters/forms.py:44 56 + #: letters/forms.py:38 48 57 msgid "Letter Body" 49 58 msgstr "Brieftext" 50 59 51 - #: letters/forms.py:45 52 - #, fuzzy 53 - #| msgid "To Representative:" 60 + #: letters/forms.py:39 54 61 msgid "To Representative" 55 - msgstr "An Abgeordnete:" 62 + msgstr "An Abgeordnete" 56 63 57 - #: letters/forms.py:48 64 + #: letters/forms.py:42 58 65 msgid "Describe your concern briefly" 59 66 msgstr "Beschreiben Sie Ihr Anliegen kurz" 60 67 61 - #: letters/forms.py:49 62 - msgid "Write your letter here" 63 - msgstr "Schreiben Sie hier Ihren Brief" 68 + #: letters/forms.py:43 69 + msgid "" 70 + "Write your letter here. Markdown formatting (e.g. **bold**, _italic_) is " 71 + "supported." 72 + msgstr "" 73 + "Schreiben Sie hier Ihren Brief. Markdown-Formatierung (z.B. **fett**, _kursiv_) " 74 + "wird unterstรผtzt." 64 75 65 - #: letters/forms.py:52 76 + #: letters/forms.py:46 66 77 msgid "Letter title" 67 78 msgstr "Brieftitel" 68 79 69 - #: letters/forms.py:56 80 + #: letters/forms.py:50 70 81 msgid "Write your letter here..." 71 82 msgstr "Schreiben Sie hier Ihren Brief..." 72 83 73 - #: letters/forms.py:156 84 + #: letters/forms.py:110 74 85 msgid "Comment (optional)" 75 86 msgstr "Kommentar (optional)" 76 87 77 - #: letters/forms.py:159 88 + #: letters/forms.py:113 78 89 msgid "Add a personal note to your signature" 79 90 msgstr "Fรผgen Sie Ihrer Unterschrift eine persรถnliche Notiz hinzu" 80 91 81 - #: letters/forms.py:165 92 + #: letters/forms.py:119 82 93 msgid "Optional: Add your comment..." 83 94 msgstr "Optional: Fรผgen Sie Ihren Kommentar hinzu..." 84 95 85 - #: letters/forms.py:177 96 + #: letters/forms.py:131 86 97 msgid "Reason" 87 98 msgstr "Grund" 88 99 89 - #: letters/forms.py:178 100 + #: letters/forms.py:132 90 101 msgid "Description" 91 102 msgstr "Beschreibung" 92 103 93 - #: letters/forms.py:181 104 + #: letters/forms.py:135 94 105 msgid "Please provide details about why you are reporting this letter" 95 106 msgstr "Bitte geben Sie Details an, warum Sie diesen Brief melden" 96 107 97 - #: letters/forms.py:188 108 + #: letters/forms.py:142 98 109 msgid "Please describe the issue..." 99 110 msgstr "Bitte beschreiben Sie das Problem..." 100 111 101 - #: letters/forms.py:198 letters/templates/letters/letter_list.html:19 112 + #: letters/forms.py:152 letters/templates/letters/letter_list.html:19 102 113 msgid "Search" 103 114 msgstr "Suchen" 104 115 105 - #: letters/forms.py:201 letters/templates/letters/letter_list.html:18 116 + #: letters/forms.py:155 letters/templates/letters/letter_list.html:18 106 117 msgid "Search letters..." 107 118 msgstr "Briefe suchen..." 108 119 109 - #: letters/forms.py:207 120 + #: letters/forms.py:161 110 121 msgid "Tag" 111 122 msgstr "Schlagwort" 112 123 113 - #: letters/forms.py:210 124 + #: letters/forms.py:164 114 125 msgid "Filter by tag..." 115 126 msgstr "Nach Schlagwort filtern..." 116 127 128 + #: letters/forms.py:180 129 + msgid "Bundestag constituency" 130 + msgstr "Bundestags-Wahlkreis" 131 + 132 + #: letters/forms.py:181 133 + msgid "Pick your Bundestag direct mandate constituency (Wahlkreis)." 134 + msgstr "Wรคhlen Sie Ihren Bundestags-Direktwahlkreis (Wahlkreis)." 135 + 136 + #: letters/forms.py:182 letters/forms.py:189 137 + msgid "Select constituency" 138 + msgstr "Wahlkreis auswรคhlen" 139 + 140 + #: letters/forms.py:187 141 + msgid "State parliament constituency" 142 + msgstr "Landtags-Wahlkreis" 143 + 144 + #: letters/forms.py:188 145 + msgid "Optionally pick your Landtag constituency if applicable." 146 + msgstr "Wรคhlen Sie optional Ihren Landtags-Wahlkreis, falls zutreffend." 147 + 148 + #: letters/forms.py:240 149 + msgid "Please select at least one constituency to save your profile." 150 + msgstr "Bitte wรคhlen Sie mindestens einen Wahlkreis aus, um Ihr Profil zu speichern." 151 + 152 + #: letters/forms.py:252 153 + msgid "StraรŸe und Hausnummer" 154 + msgstr "StraรŸe und Hausnummer" 155 + 156 + #: letters/forms.py:255 157 + msgid "z.B. Unter den Linden 77" 158 + msgstr "z.B. Unter den Linden 77" 159 + 160 + #: letters/forms.py:261 161 + msgid "Postleitzahl" 162 + msgstr "Postleitzahl" 163 + 164 + #: letters/forms.py:264 165 + msgid "z.B. 10117" 166 + msgstr "z.B. 10117" 167 + 168 + #: letters/forms.py:270 169 + msgid "Stadt" 170 + msgstr "Stadt" 171 + 172 + #: letters/forms.py:273 173 + msgid "z.B. Berlin" 174 + msgstr "z.B. Berlin" 175 + 176 + #: letters/forms.py:304 177 + msgid "" 178 + "Bitte geben Sie eine vollstรคndige Adresse ein (StraรŸe, PLZ und Stadt) oder " 179 + "lassen Sie alle Felder leer." 180 + msgstr "" 181 + "Bitte geben Sie eine vollstรคndige Adresse ein (StraรŸe, PLZ und Stadt) oder " 182 + "lassen Sie alle Felder leer." 183 + 117 184 # Model choices - Constituency 118 - #: letters/models.py:13 185 + #: letters/models.py:15 119 186 msgid "European Union" 120 187 msgstr "Europรคische Union" 121 188 122 - #: letters/models.py:14 189 + #: letters/models.py:16 123 190 msgid "Federal" 124 191 msgstr "Bund" 125 192 126 - #: letters/models.py:15 193 + #: letters/models.py:17 127 194 msgid "State" 128 195 msgstr "Land" 129 196 130 - #: letters/models.py:16 197 + #: letters/models.py:18 131 198 msgid "Local" 132 199 msgstr "Kommune" 133 200 134 - #: letters/models.py:19 201 + #: letters/models.py:21 135 202 msgid "Name of the parliament" 136 - msgstr "" 203 + msgstr "Name des Parlaments" 137 204 138 - #: letters/models.py:23 205 + #: letters/models.py:25 139 206 msgid "e.g., 'Bundestag', 'Bayerischer Landtag', 'Gemeinderat Mรผnchen'" 140 - msgstr "" 207 + msgstr "z.B. 'Bundestag', 'Bayerischer Landtag', 'Gemeinderat Mรผnchen'" 141 208 142 - #: letters/models.py:27 209 + #: letters/models.py:29 143 210 msgid "Geographic identifier (state code, municipality code, etc.)" 144 - msgstr "" 211 + msgstr "Geografische Kennung (Bundeslandkรผrzel, Gemeindeschlรผssel, etc.)" 145 212 146 - #: letters/models.py:35 213 + #: letters/models.py:37 147 214 msgid "For hierarchical relationships (e.g., local within state)" 148 - msgstr "" 215 + msgstr "Fรผr hierarchische Beziehungen (z.B. Kommune innerhalb eines Bundeslandes)" 216 + 217 + #: letters/models.py:43 letters/models.py:70 letters/models.py:105 218 + #: letters/models.py:165 letters/models.py:372 letters/models.py:421 219 + msgid "Last time this was synced from external API" 220 + msgstr "Zeitpunkt der letzten Synchronisierung von externer API" 149 221 150 - #: letters/models.py:44 222 + #: letters/models.py:47 letters/templates/letters/committee_detail.html:70 151 223 msgid "Parliament" 152 - msgstr "" 224 + msgstr "Parlament" 153 225 154 - #: letters/models.py:45 226 + #: letters/models.py:48 155 227 msgid "Parliaments" 156 - msgstr "" 157 - 158 - #: letters/models.py:80 159 - #, fuzzy 160 - #| msgid "Federal" 161 - msgid "Federal district" 162 - msgstr "Bund" 163 - 164 - #: letters/models.py:81 165 - msgid "State district" 166 - msgstr "" 167 - 168 - #: letters/models.py:82 169 - msgid "Regional district" 170 - msgstr "" 228 + msgstr "Parlamente" 171 229 172 - #: letters/models.py:114 230 + #: letters/models.py:84 173 231 msgid "Federal electoral district" 174 - msgstr "" 232 + msgstr "Bundeswahlkreis" 175 233 176 - #: letters/models.py:115 234 + #: letters/models.py:85 177 235 msgid "Bundestag state list" 178 - msgstr "" 236 + msgstr "Bundestag-Landesliste" 179 237 180 - #: letters/models.py:116 238 + #: letters/models.py:86 181 239 msgid "Bundestag federal list" 182 - msgstr "" 240 + msgstr "Bundestag-Bundesliste" 183 241 184 - #: letters/models.py:117 242 + #: letters/models.py:87 185 243 msgid "State electoral district" 186 - msgstr "" 244 + msgstr "Landeswahlkreis" 187 245 188 - #: letters/models.py:118 246 + #: letters/models.py:88 189 247 msgid "State regional list" 190 - msgstr "" 248 + msgstr "Regionale Landesliste" 191 249 192 - #: letters/models.py:119 250 + #: letters/models.py:89 193 251 msgid "State wide list" 194 - msgstr "" 252 + msgstr "Landesweite Liste" 195 253 196 - #: letters/models.py:120 254 + #: letters/models.py:90 197 255 msgid "EU at large" 198 - msgstr "" 256 + msgstr "EU insgesamt" 199 257 200 - #: letters/models.py:153 258 + #: letters/models.py:119 201 259 msgid "Direct mandate" 202 - msgstr "" 260 + msgstr "Direktmandat" 203 261 204 - #: letters/models.py:154 262 + #: letters/models.py:120 205 263 msgid "State list mandate" 206 - msgstr "" 264 + msgstr "Landeslisten-Mandat" 207 265 208 - #: letters/models.py:155 266 + #: letters/models.py:121 209 267 msgid "State regional list mandate" 210 - msgstr "" 268 + msgstr "Regionales Landeslisten-Mandat" 211 269 212 - #: letters/models.py:156 270 + #: letters/models.py:122 213 271 msgid "Federal list mandate" 214 - msgstr "" 272 + msgstr "Bundeslisten-Mandat" 215 273 216 - #: letters/models.py:157 274 + #: letters/models.py:123 217 275 msgid "EU list mandate" 218 - msgstr "" 276 + msgstr "EU-Listen-Mandat" 219 277 220 278 # Model choices - Letter 221 - #: letters/models.py:422 279 + #: letters/models.py:444 222 280 msgid "Draft" 223 281 msgstr "Entwurf" 224 282 225 - #: letters/models.py:423 283 + #: letters/models.py:445 226 284 msgid "Published" 227 285 msgstr "Verรถffentlicht" 228 286 229 - #: letters/models.py:424 287 + #: letters/models.py:446 230 288 msgid "Flagged for Review" 231 289 msgstr "Zur รœberprรผfung markiert" 232 290 233 - #: letters/models.py:425 291 + #: letters/models.py:447 234 292 msgid "Removed" 235 293 msgstr "Entfernt" 236 294 237 - #: letters/services.py:1149 295 + #: letters/models.py:487 296 + msgid "Deleted user" 297 + msgstr "Gelรถschter Benutzer" 298 + 299 + #: letters/services.py:2451 238 300 #, python-format 239 301 msgid "Detected policy area: %(topic)s." 240 - msgstr "" 302 + msgstr "Erkannter Politikbereich: %(topic)s." 241 303 242 - #: letters/services.py:1154 243 - #, fuzzy, python-format 244 - #| msgid "Use your PLZ to narrow down representatives from your constituency." 304 + #: letters/services.py:2456 305 + #, python-format 245 306 msgid "Prioritising representatives for %(constituencies)s." 246 - msgstr "" 247 - "Verwenden Sie Ihre PLZ, um Abgeordnete aus Ihrem Wahlkreis einzugrenzen." 307 + msgstr "Priorisiere Abgeordnete fรผr %(constituencies)s." 248 308 249 - #: letters/services.py:1159 309 + #: letters/services.py:2461 250 310 #, python-format 251 311 msgid "Filtering by state %(state)s." 252 - msgstr "" 312 + msgstr "Filtere nach Bundesland %(state)s." 253 313 254 - #: letters/services.py:1163 314 + #: letters/services.py:2465 255 315 #, python-format 256 316 msgid "" 257 317 "Postal code %(plz)s had no direct match; showing broader representatives." 258 318 msgstr "" 319 + "Postleitzahl %(plz)s hatte keine direkte รœbereinstimmung; zeige allgemeinere Abgeordnete." 259 320 260 - #: letters/services.py:1167 261 - #, fuzzy 262 - #| msgid "No external resources available for this representative." 321 + #: letters/services.py:2469 263 322 msgid "Showing generally relevant representatives." 264 - msgstr "Keine externen Ressourcen fรผr diesen Abgeordneten verfรผgbar." 323 + msgstr "Zeige allgemein relevante Abgeordnete." 324 + 325 + #: letters/templates/letters/account_activation_invalid.html:4 326 + #: letters/templates/letters/account_activation_invalid.html:8 327 + msgid "Activation link invalid" 328 + msgstr "Aktivierungslink ungรผltig" 329 + 330 + #: letters/templates/letters/account_activation_invalid.html:10 331 + msgid "" 332 + "We could not verify your activation link. It may have already been used or " 333 + "expired." 334 + msgstr "" 335 + "Wir konnten Ihren Aktivierungslink nicht verifizieren. Er wurde mรถglicherweise bereits " 336 + "verwendet oder ist abgelaufen." 337 + 338 + #: letters/templates/letters/account_activation_invalid.html:13 339 + msgid "" 340 + "If you still cannot access your account, try registering again or contact " 341 + "support." 342 + msgstr "" 343 + "Falls Sie weiterhin nicht auf Ihr Konto zugreifen kรถnnen, versuchen Sie sich erneut zu " 344 + "registrieren oder kontaktieren Sie den Support." 345 + 346 + #: letters/templates/letters/account_activation_invalid.html:15 347 + msgid "Register again" 348 + msgstr "Erneut registrieren" 349 + 350 + #: letters/templates/letters/account_activation_sent.html:4 351 + msgid "Activate your account" 352 + msgstr "Aktivieren Sie Ihr Konto" 353 + 354 + #: letters/templates/letters/account_activation_sent.html:8 355 + #: letters/templates/letters/password_reset_done.html:8 356 + msgid "Check your inbox" 357 + msgstr "Prรผfen Sie Ihren Posteingang" 358 + 359 + #: letters/templates/letters/account_activation_sent.html:10 360 + msgid "" 361 + "We sent you an email with a confirmation link. Please click it to activate " 362 + "your account." 363 + msgstr "" 364 + "Wir haben Ihnen eine E-Mail mit einem Bestรคtigungslink gesendet. Bitte klicken Sie " 365 + "darauf, um Ihr Konto zu aktivieren." 366 + 367 + #: letters/templates/letters/account_activation_sent.html:13 368 + msgid "" 369 + "If you do not receive the email within a few minutes, check your spam folder " 370 + "or try registering again." 371 + msgstr "" 372 + "Falls Sie die E-Mail nicht innerhalb weniger Minuten erhalten, prรผfen Sie Ihren " 373 + "Spam-Ordner oder versuchen Sie sich erneut zu registrieren." 374 + 375 + #: letters/templates/letters/account_activation_sent.html:15 376 + msgid "Back to homepage" 377 + msgstr "Zurรผck zur Startseite" 378 + 379 + #: letters/templates/letters/account_delete_confirm.html:4 380 + #: letters/templates/letters/profile.html:142 381 + msgid "Delete account" 382 + msgstr "Konto lรถschen" 383 + 384 + #: letters/templates/letters/account_delete_confirm.html:8 385 + msgid "Delete your account" 386 + msgstr "Lรถschen Sie Ihr Konto" 387 + 388 + #: letters/templates/letters/account_delete_confirm.html:10 389 + msgid "" 390 + "Deleting your account will remove your personal data and signatures. Letters " 391 + "you have published stay online but are shown without your name." 392 + msgstr "" 393 + "Das Lรถschen Ihres Kontos entfernt Ihre persรถnlichen Daten und Unterschriften. Von Ihnen " 394 + "verรถffentlichte Briefe bleiben online, werden aber ohne Ihren Namen angezeigt." 395 + 396 + #: letters/templates/letters/account_delete_confirm.html:14 397 + msgid "Yes, delete my account" 398 + msgstr "Ja, mein Konto lรถschen" 399 + 400 + #: letters/templates/letters/account_delete_confirm.html:15 401 + #: letters/templates/letters/letter_form.html:61 402 + msgid "Cancel" 403 + msgstr "Abbrechen" 265 404 266 405 # Navigation 267 - #: letters/templates/letters/base.html:124 406 + #: letters/templates/letters/base.html:140 268 407 msgid "Letters" 269 408 msgstr "Briefe" 270 409 271 - #: letters/templates/letters/base.html:126 272 - #: letters/templates/letters/representative_detail.html:196 410 + #: letters/templates/letters/base.html:141 411 + msgid "Competencies" 412 + msgstr "Kompetenzen" 413 + 414 + #: letters/templates/letters/base.html:143 415 + #: letters/templates/letters/representative_detail.html:149 273 416 msgid "Write Letter" 274 417 msgstr "Brief schreiben" 275 418 276 - #: letters/templates/letters/base.html:127 419 + #: letters/templates/letters/base.html:144 277 420 #: letters/templates/letters/profile.html:4 278 421 msgid "Profile" 279 422 msgstr "Profil" 280 423 281 - #: letters/templates/letters/base.html:128 424 + #: letters/templates/letters/base.html:145 282 425 msgid "Logout" 283 426 msgstr "Abmelden" 284 427 285 - #: letters/templates/letters/base.html:130 286 - #: letters/templates/letters/letter_detail.html:46 287 - #: letters/templates/letters/letter_detail.html:80 428 + #: letters/templates/letters/base.html:147 429 + #: letters/templates/letters/letter_detail.html:47 430 + #: letters/templates/letters/letter_detail.html:81 288 431 #: letters/templates/letters/login.html:4 289 432 #: letters/templates/letters/login.html:8 290 433 #: letters/templates/letters/login.html:33 291 434 msgid "Login" 292 435 msgstr "Anmelden" 293 436 294 - #: letters/templates/letters/base.html:131 437 + #: letters/templates/letters/base.html:148 295 438 #: letters/templates/letters/register.html:4 296 439 #: letters/templates/letters/register.html:8 297 - #: letters/templates/letters/register.html:65 440 + #: letters/templates/letters/register.html:66 298 441 msgid "Register" 299 442 msgstr "Registrieren" 300 443 301 - #: letters/templates/letters/base.html:134 444 + #: letters/templates/letters/base.html:151 302 445 msgid "Admin" 303 446 msgstr "Admin" 304 447 448 + #: letters/templates/letters/base.html:157 449 + msgid "Select language" 450 + msgstr "Sprache auswรคhlen" 451 + 305 452 # Footer 306 - #: letters/templates/letters/base.html:154 453 + #: letters/templates/letters/base.html:182 307 454 msgid "Empowering citizens to write to their representatives" 308 455 msgstr "Bรผrgern ermรถglichen, an ihre Abgeordneten zu schreiben" 309 456 457 + #: letters/templates/letters/committee_detail.html:22 458 + msgid "Related Topics" 459 + msgstr "Verwandte Themen" 460 + 461 + #: letters/templates/letters/committee_detail.html:36 462 + msgid "Members" 463 + msgstr "Mitglieder" 464 + 465 + #: letters/templates/letters/committee_detail.html:46 466 + msgid "Role" 467 + msgstr "Rolle" 468 + 469 + #: letters/templates/letters/committee_detail.html:48 470 + #: letters/templates/letters/representative_detail.html:61 471 + msgid "Active" 472 + msgstr "Aktiv" 473 + 474 + #: letters/templates/letters/committee_detail.html:51 475 + #: letters/templates/letters/partials/representative_card.html:29 476 + msgid "Since" 477 + msgstr "Seit" 478 + 479 + #: letters/templates/letters/committee_detail.html:58 480 + msgid "No members recorded for this committee." 481 + msgstr "Fรผr diesen Ausschuss sind keine Mitglieder verzeichnet." 482 + 483 + #: letters/templates/letters/committee_detail.html:67 484 + msgid "Committee Info" 485 + msgstr "Ausschussinformationen" 486 + 487 + #: letters/templates/letters/committee_detail.html:71 488 + msgid "Term" 489 + msgstr "Amtszeit" 490 + 491 + #: letters/templates/letters/committee_detail.html:74 492 + #: letters/templates/letters/representative_detail.html:105 493 + msgid "View on Abgeordnetenwatch" 494 + msgstr "Auf Abgeordnetenwatch ansehen" 495 + 310 496 #: letters/templates/letters/letter_detail.html:10 311 497 #: letters/templates/letters/partials/letter_card.html:5 312 498 msgid "By" 313 499 msgstr "Von" 314 500 315 - #: letters/templates/letters/letter_detail.html:11 316 - #: letters/templates/letters/partials/letter_card.html:6 317 - #: letters/templates/letters/profile.html:68 318 - msgid "To" 319 - msgstr "An" 320 - 321 - #: letters/templates/letters/letter_detail.html:28 501 + #: letters/templates/letters/letter_detail.html:29 322 502 #, python-format 323 503 msgid "Signatures (%(counter)s)" 324 504 msgid_plural "Signatures (%(counter)s)" 325 505 msgstr[0] "Unterschrift (%(counter)s)" 326 506 msgstr[1] "Unterschriften (%(counter)s)" 327 507 328 - #: letters/templates/letters/letter_detail.html:30 329 - #, fuzzy, python-format 330 - #| msgid "<strong>%(counter)s</strong> constituent of %(constituency)s" 331 - #| msgid_plural "<strong>%(counter)s</strong> constituents of %(constituency)s" 508 + #: letters/templates/letters/letter_detail.html:31 509 + #, python-format 332 510 msgid "%(counter)s constituent of %(constituency_name)s" 333 511 msgid_plural "%(counter)s constituents of %(constituency_name)s" 334 512 msgstr[0] "%(counter)s Wรคhler aus %(constituency_name)s" 335 513 msgstr[1] "%(counter)s Wรคhler aus %(constituency_name)s" 336 514 337 - #: letters/templates/letters/letter_detail.html:31 338 - #, fuzzy, python-format 339 - #| msgid "%(counter)s verified" 515 + #: letters/templates/letters/letter_detail.html:32 516 + #, python-format 340 517 msgid "%(counter)s other verified" 341 518 msgid_plural "%(counter)s other verified" 342 - msgstr[0] "%(counter)s verifiziert" 343 - msgstr[1] "%(counter)s verifiziert" 519 + msgstr[0] "%(counter)s weitere verifiziert" 520 + msgstr[1] "%(counter)s weitere verifiziert" 344 521 345 - #: letters/templates/letters/letter_detail.html:32 522 + #: letters/templates/letters/letter_detail.html:33 346 523 #, python-format 347 524 msgid "%(counter)s unverified" 348 525 msgid_plural "%(counter)s unverified" 349 526 msgstr[0] "%(counter)s nicht verifiziert" 350 527 msgstr[1] "%(counter)s nicht verifiziert" 351 528 352 - #: letters/templates/letters/letter_detail.html:40 529 + #: letters/templates/letters/letter_detail.html:41 353 530 msgid "Sign this letter" 354 531 msgstr "Brief unterzeichnen" 355 532 356 - #: letters/templates/letters/letter_detail.html:43 533 + #: letters/templates/letters/letter_detail.html:44 357 534 msgid "You have signed this letter" 358 535 msgstr "Sie haben diesen Brief unterzeichnet" 359 536 360 - #: letters/templates/letters/letter_detail.html:46 361 - #, fuzzy 362 - #| msgid "Login to sign this letter" 537 + #: letters/templates/letters/letter_detail.html:47 363 538 msgid "to sign this letter" 364 - msgstr "Anmelden um zu unterzeichnen" 539 + msgstr "um diesen Brief zu unterzeichnen" 365 540 366 - #: letters/templates/letters/letter_detail.html:69 541 + #: letters/templates/letters/letter_detail.html:58 542 + msgid "โœ“ Verified Constituent" 543 + msgstr "โœ“ Verifizierter Wรคhler" 544 + 545 + #: letters/templates/letters/letter_detail.html:60 546 + msgid "โœ“ Verified" 547 + msgstr "โœ“ Verifiziert" 548 + 549 + #: letters/templates/letters/letter_detail.html:70 367 550 msgid "No signatures yet. Be the first to sign!" 368 551 msgstr "Noch keine Unterschriften. Seien Sie der Erste!" 369 552 370 - #: letters/templates/letters/letter_detail.html:74 371 - #: letters/templates/letters/letter_detail.html:78 553 + #: letters/templates/letters/letter_detail.html:75 554 + #: letters/templates/letters/letter_detail.html:79 372 555 msgid "Report this letter" 373 556 msgstr "Brief melden" 374 557 375 - #: letters/templates/letters/letter_detail.html:75 558 + #: letters/templates/letters/letter_detail.html:76 376 559 msgid "If you believe this letter violates our guidelines, please report it." 377 - msgstr "" 560 + msgstr "Falls Sie glauben, dass dieser Brief gegen unsere Richtlinien verstรถรŸt, melden Sie ihn bitte." 378 561 379 - #: letters/templates/letters/letter_detail.html:80 380 - #, fuzzy 381 - #| msgid "Report this letter" 562 + #: letters/templates/letters/letter_detail.html:81 382 563 msgid "to report this letter" 383 - msgstr "Brief melden" 564 + msgstr "um diesen Brief zu melden" 384 565 385 - #: letters/templates/letters/letter_detail.html:85 566 + #: letters/templates/letters/letter_detail.html:86 386 567 msgid "Back to all letters" 387 568 msgstr "Zurรผck zu allen Briefen" 388 569 389 570 #: letters/templates/letters/letter_form.html:4 390 - #: letters/templates/letters/representative_detail.html:190 571 + #: letters/templates/letters/representative_detail.html:143 391 572 msgid "Write a Letter" 392 573 msgstr "Brief schreiben" 393 574 394 575 # Letter form 395 - #: letters/templates/letters/letter_form.html:11 576 + #: letters/templates/letters/letter_form.html:10 396 577 msgid "Write an Open Letter" 397 578 msgstr "Einen offenen Brief schreiben" 398 579 399 - #: letters/templates/letters/letter_form.html:13 580 + #: letters/templates/letters/letter_form.html:12 400 581 msgid "" 401 - "Write an open letter to a German political representative. Your letter will " 402 - "be published publicly and others can sign it." 582 + "Write an open letter to a political representative. Your letter will be " 583 + "published publicly so others can read and sign it." 403 584 msgstr "" 404 - "Schreiben Sie einen offenen Brief an einen deutschen Abgeordneten. Ihr Brief " 405 - "wird รถffentlich verรถffentlicht und andere kรถnnen ihn unterzeichnen." 585 + "Schreiben Sie einen offenen Brief an einen Abgeordneten. Ihr Brief wird " 586 + "รถffentlich verรถffentlicht, damit andere ihn lesen und unterzeichnen kรถnnen." 587 + 588 + #: letters/templates/letters/letter_form.html:16 589 + msgid "Before you write" 590 + msgstr "Bevor Sie schreiben" 591 + 592 + #: letters/templates/letters/letter_form.html:18 593 + msgid "Be thoughtful but feel free to be critical." 594 + msgstr "Seien Sie nachdenklich, aber scheuen Sie sich nicht, kritisch zu sein." 595 + 596 + #: letters/templates/letters/letter_form.html:19 597 + msgid "Representatives are humans tooโ€”stay respectful." 598 + msgstr "Abgeordnete sind auch Menschen โ€“ bleiben Sie respektvoll." 406 599 407 600 #: letters/templates/letters/letter_form.html:20 601 + msgid "Keep your arguments clear and concise." 602 + msgstr "Halten Sie Ihre Argumente klar und prรคgnant." 603 + 604 + #: letters/templates/letters/letter_form.html:21 605 + msgid "No insults or hate speech." 606 + msgstr "Keine Beleidigungen oder Hassrede." 607 + 608 + #: letters/templates/letters/letter_form.html:22 609 + msgid "Stay within the bounds of the Grundgesetz when making demands." 610 + msgstr "Bleiben Sie bei Forderungen im Rahmen des Grundgesetzes." 611 + 612 + #: letters/templates/letters/letter_form.html:30 408 613 msgid "Title:" 409 614 msgstr "Titel:" 410 615 411 - #: letters/templates/letters/letter_form.html:26 616 + #: letters/templates/letters/letter_form.html:36 412 617 msgid "" 413 - "Describe your concern. We'll suggest the right representatives based on your " 414 - "title." 415 - msgstr "" 416 - "Beschreiben Sie Ihr Anliegen. Wir schlagen Ihnen passende Abgeordnete " 417 - "basierend auf Ihrem Titel vor." 418 - 419 - #: letters/templates/letters/letter_form.html:31 420 - msgid "Your postal code (PLZ):" 421 - msgstr "Ihre Postleitzahl (PLZ):" 422 - 423 - #: letters/templates/letters/letter_form.html:37 424 - msgid "We'll use this PLZ to highlight representatives from your constituency." 618 + "Describe your concern in a sentence; we'll use it to suggest representatives." 425 619 msgstr "" 426 - "Wir verwenden diese PLZ, um Abgeordnete aus Ihrem Wahlkreis hervorzuheben." 620 + "Beschreiben Sie Ihr Anliegen in einem Satz; wir verwenden ihn, um Ihnen Abgeordnete " 621 + "vorzuschlagen." 427 622 428 - #: letters/templates/letters/letter_form.html:45 623 + #: letters/templates/letters/letter_form.html:41 429 624 msgid "To Representative:" 430 625 msgstr "An Abgeordnete:" 431 626 432 - #: letters/templates/letters/letter_form.html:51 433 - msgid "Or select from suggestions on the right โ†’" 434 - msgstr "Oder wรคhlen Sie aus den Vorschlรคgen rechts โ†’" 627 + #: letters/templates/letters/letter_form.html:47 628 + msgid "" 629 + "Already know who to address? Pick them here. Otherwise, use the suggestions " 630 + "below." 631 + msgstr "" 632 + "Wissen Sie bereits, wen Sie ansprechen mรถchten? Wรคhlen Sie hier aus. Andernfalls nutzen " 633 + "Sie die Vorschlรคge unten." 435 634 436 - #: letters/templates/letters/letter_form.html:56 635 + #: letters/templates/letters/letter_form.html:53 437 636 msgid "Letter Body:" 438 637 msgstr "Brieftext:" 439 638 440 - #: letters/templates/letters/letter_form.html:64 441 - msgid "Tags (optional):" 442 - msgstr "Schlagwรถrter (optional):" 443 - 444 - #: letters/templates/letters/letter_form.html:71 639 + #: letters/templates/letters/letter_form.html:60 445 640 msgid "Publish Letter" 446 641 msgstr "Brief verรถffentlichen" 447 642 448 - #: letters/templates/letters/letter_form.html:72 449 - msgid "Cancel" 450 - msgstr "Abbrechen" 451 - 452 - #: letters/templates/letters/letter_form.html:81 643 + #: letters/templates/letters/letter_form.html:67 453 644 msgid "Smart Suggestions" 454 645 msgstr "Intelligente Vorschlรคge" 455 646 456 - #: letters/templates/letters/letter_form.html:83 457 - #, fuzzy 458 - #| msgid "" 459 - #| "Type your letter title and we'll help you find the right representative " 460 - #| "and show similar letters." 647 + #: letters/templates/letters/letter_form.html:69 461 648 msgid "" 462 - "Add a title and your PLZ to see tailored representatives, tags, and similar " 463 - "letters." 649 + "Type your title and we'll use your verified profile to suggest " 650 + "representatives, topics, and related letters." 464 651 msgstr "" 465 - "Geben Sie Ihren Brieftitel ein und wir helfen Ihnen, den richtigen " 466 - "Abgeordneten zu finden und zeigen รคhnliche Briefe." 652 + "Geben Sie Ihren Titel ein und wir verwenden Ihr verifiziertes Profil, um Ihnen " 653 + "Abgeordnete, Themen und verwandte Briefe vorzuschlagen." 467 654 468 - #: letters/templates/letters/letter_form.html:98 655 + #: letters/templates/letters/letter_form.html:81 469 656 msgid "Loading..." 470 657 msgstr "Lรคdt..." 471 658 472 - #: letters/templates/letters/letter_form.html:100 659 + #: letters/templates/letters/letter_form.html:83 473 660 msgid "Analyzing your title..." 474 661 msgstr "Analysiere Ihren Titel..." 475 662 ··· 510 697 msgid "Popular tags:" 511 698 msgstr "Beliebte Schlagwรถrter:" 512 699 700 + #: letters/templates/letters/letter_list.html:41 701 + msgid "Previous" 702 + msgstr "Zurรผck" 703 + 704 + #: letters/templates/letters/letter_list.html:43 705 + #, python-format 706 + msgid "Page %(page)s of %(total)s" 707 + msgstr "Seite %(page)s von %(total)s" 708 + 709 + #: letters/templates/letters/letter_list.html:45 710 + msgid "Next" 711 + msgstr "Weiter" 712 + 713 + #: letters/templates/letters/letter_list.html:51 714 + msgid "No letters found." 715 + msgstr "Keine Briefe gefunden." 716 + 717 + #: letters/templates/letters/letter_list.html:51 718 + msgid "Be the first to write one!" 719 + msgstr "Schreiben Sie den ersten!" 720 + 513 721 # Login page 514 722 #: letters/templates/letters/login.html:14 515 - #: letters/templates/letters/register.html:14 723 + #: letters/templates/letters/register.html:15 516 724 msgid "Username:" 517 725 msgstr "Benutzername:" 518 726 519 727 #: letters/templates/letters/login.html:22 520 - #: letters/templates/letters/register.html:46 728 + #: letters/templates/letters/register.html:47 521 729 msgid "Password:" 522 730 msgstr "Passwort:" 523 731 524 732 #: letters/templates/letters/login.html:37 733 + msgid "Forgot your password?" 734 + msgstr "Passwort vergessen?" 735 + 736 + #: letters/templates/letters/login.html:41 525 737 msgid "Don't have an account?" 526 738 msgstr "Noch kein Konto?" 527 739 528 - #: letters/templates/letters/login.html:37 740 + #: letters/templates/letters/login.html:41 529 741 msgid "Register here" 530 742 msgstr "Hier registrieren" 531 743 744 + #: letters/templates/letters/partials/letter_card.html:6 745 + msgid "To" 746 + msgstr "An" 747 + 532 748 # Plurals for signatures 533 749 #: letters/templates/letters/partials/letter_card.html:20 534 - #: letters/templates/letters/partials/suggestions.html:126 535 750 #, python-format 536 751 msgid "%(counter)s signature" 537 752 msgid_plural "%(counter)s signatures" ··· 539 754 msgstr[1] "%(counter)s Unterschriften" 540 755 541 756 #: letters/templates/letters/partials/letter_card.html:20 542 - #, fuzzy, python-format 543 - #| msgid "%(counter)s verified" 757 + #, python-format 544 758 msgid "%(counter)s verified" 545 759 msgid_plural "%(counter)s verified" 546 760 msgstr[0] "%(counter)s verifiziert" 547 761 msgstr[1] "%(counter)s verifiziert" 548 762 763 + #: letters/templates/letters/partials/representative_card.html:21 764 + #: letters/templates/letters/partials/representative_card.html:23 765 + msgid "Constituency" 766 + msgstr "Wahlkreis" 767 + 768 + #: letters/templates/letters/partials/representative_card.html:27 769 + msgid "Mandate" 770 + msgstr "Mandat" 771 + 772 + #: letters/templates/letters/partials/representative_card.html:34 773 + msgid "Focus" 774 + msgstr "Schwerpunkt" 775 + 776 + #: letters/templates/letters/partials/representative_card.html:34 777 + msgid "self-declared" 778 + msgstr "selbst erklรคrt" 779 + 780 + #: letters/templates/letters/partials/representative_card.html:45 781 + msgid "Committees" 782 + msgstr "Ausschรผsse" 783 + 784 + #: letters/templates/letters/partials/representative_card.html:57 785 + msgid "Email" 786 + msgstr "E-Mail" 787 + 788 + #: letters/templates/letters/partials/representative_card.html:60 789 + msgid "Website" 790 + msgstr "Webseite" 791 + 792 + #: letters/templates/letters/partials/representative_card.html:66 793 + msgid "View profile" 794 + msgstr "Profil ansehen" 795 + 796 + #: letters/templates/letters/partials/suggestions.html:10 797 + msgid "" 798 + "We couldn't match you to a constituency yet. Update your profile " 799 + "verification to see local representatives." 800 + msgstr "" 801 + "Wir konnten Sie noch keinem Wahlkreis zuordnen. Aktualisieren Sie Ihre Profilverifizierung, " 802 + "um lokale Abgeordnete zu sehen." 803 + 549 804 # Suggestions partial 550 - #: letters/templates/letters/partials/suggestions.html:11 805 + #: letters/templates/letters/partials/suggestions.html:16 551 806 msgid "Our Interpretation" 552 807 msgstr "Unsere Interpretation" 553 808 554 - #: letters/templates/letters/partials/suggestions.html:16 809 + #: letters/templates/letters/partials/suggestions.html:21 555 810 msgid "Topic:" 556 811 msgstr "Thema:" 557 812 558 - #: letters/templates/letters/partials/suggestions.html:23 813 + #: letters/templates/letters/partials/suggestions.html:28 559 814 msgid "No specific policy area detected. Try adding more keywords." 560 815 msgstr "" 561 816 "Kein spezifischer Politikbereich erkannt. Versuchen Sie, mehr " 562 817 "Schlรผsselwรถrter hinzuzufรผgen." 563 818 564 - #: letters/templates/letters/partials/suggestions.html:31 565 - msgid "Suggested Representatives" 566 - msgstr "Vorgeschlagene Abgeordnete" 819 + #: letters/templates/letters/partials/suggestions.html:37 820 + msgid "Related Keywords" 821 + msgstr "Verwandte Schlagwรถrter" 822 + 823 + #: letters/templates/letters/partials/suggestions.html:51 824 + msgid "Your Direct Representatives" 825 + msgstr "Ihre direkten Abgeordneten" 826 + 827 + #: letters/templates/letters/partials/suggestions.html:56 828 + msgid "These representatives directly represent your constituency:" 829 + msgstr "Diese Abgeordneten vertreten Ihren Wahlkreis direkt:" 567 830 568 - #: letters/templates/letters/partials/suggestions.html:37 831 + #: letters/templates/letters/partials/suggestions.html:67 832 + #: letters/templates/letters/partials/suggestions.html:102 833 + msgid "Select" 834 + msgstr "Auswรคhlen" 835 + 836 + #: letters/templates/letters/partials/suggestions.html:81 837 + msgid "Topic Experts" 838 + msgstr "Themenexperten" 839 + 840 + #: letters/templates/letters/partials/suggestions.html:86 569 841 #, python-format 570 842 msgid "" 571 - "Based on the topic \"%(topic)s\", we suggest contacting representatives from " 572 - "the %(level)s:" 843 + "These representatives are experts on \"%(topic)s\" based on their committee " 844 + "memberships:" 573 845 msgstr "" 574 - "Basierend auf dem Thema \"%(topic)s\" empfehlen wir, Abgeordnete des " 575 - "%(level)s zu kontaktieren:" 846 + "Diese Abgeordneten sind Experten fรผr \"%(topic)s\" basierend auf ihren " 847 + "Ausschussmitgliedschaften:" 576 848 577 - #: letters/templates/letters/partials/suggestions.html:51 578 - msgid "Constituency:" 579 - msgstr "Wahlkreis:" 580 - 581 - #: letters/templates/letters/partials/suggestions.html:57 849 + #: letters/templates/letters/partials/suggestions.html:95 582 850 msgid "of" 583 851 msgstr "von" 584 852 585 - #: letters/templates/letters/partials/suggestions.html:61 586 - msgid "View profile" 587 - msgstr "Profil ansehen" 853 + #: letters/templates/letters/partials/suggestions.html:116 854 + msgid "Suggested Representatives" 855 + msgstr "Vorgeschlagene Abgeordnete" 588 856 589 - #: letters/templates/letters/partials/suggestions.html:66 590 - msgid "Select" 591 - msgstr "Auswรคhlen" 592 - 593 - #: letters/templates/letters/partials/suggestions.html:73 857 + #: letters/templates/letters/partials/suggestions.html:119 594 858 msgid "" 595 859 "No representatives found. Representatives may need to be synced for this " 596 860 "governmental level." ··· 598 862 "Keine Abgeordneten gefunden. Abgeordnete mรผssen mรถglicherweise fรผr diese " 599 863 "Verwaltungsebene synchronisiert werden." 600 864 601 - #: letters/templates/letters/partials/suggestions.html:82 602 - #, fuzzy 603 - #| msgid "Suggested Representatives" 604 - msgid "Suggested Tags" 605 - msgstr "Vorgeschlagene Abgeordnete" 865 + #: letters/templates/letters/partials/suggestions.html:148 866 + msgid "Selected:" 867 + msgstr "Ausgewรคhlt:" 868 + 869 + #: letters/templates/letters/password_reset_complete.html:4 870 + #: letters/templates/letters/password_reset_complete.html:8 871 + msgid "Password updated" 872 + msgstr "Passwort aktualisiert" 873 + 874 + #: letters/templates/letters/password_reset_complete.html:9 875 + msgid "You can now sign in using your new password." 876 + msgstr "Sie kรถnnen sich jetzt mit Ihrem neuen Passwort anmelden." 877 + 878 + #: letters/templates/letters/password_reset_complete.html:10 879 + msgid "Go to login" 880 + msgstr "Zur Anmeldung" 881 + 882 + #: letters/templates/letters/password_reset_confirm.html:4 883 + #: letters/templates/letters/password_reset_confirm.html:9 884 + msgid "Choose a new password" 885 + msgstr "Wรคhlen Sie ein neues Passwort" 886 + 887 + #: letters/templates/letters/password_reset_confirm.html:13 888 + msgid "New password" 889 + msgstr "Neues Passwort" 890 + 891 + #: letters/templates/letters/password_reset_confirm.html:20 892 + msgid "Confirm password" 893 + msgstr "Passwort bestรคtigen" 894 + 895 + #: letters/templates/letters/password_reset_confirm.html:26 896 + msgid "Update password" 897 + msgstr "Passwort aktualisieren" 606 898 607 - #: letters/templates/letters/partials/suggestions.html:85 608 - msgid "Click a tag to add it to your letter." 899 + #: letters/templates/letters/password_reset_confirm.html:29 900 + msgid "Reset link invalid" 901 + msgstr "Zurรผcksetzungslink ungรผltig" 902 + 903 + #: letters/templates/letters/password_reset_confirm.html:30 904 + msgid "This password reset link is no longer valid. Please request a new one." 905 + msgstr "Dieser Passwort-Zurรผcksetzungslink ist nicht mehr gรผltig. Bitte fordern Sie einen neuen an." 906 + 907 + #: letters/templates/letters/password_reset_confirm.html:31 908 + msgid "Request new link" 909 + msgstr "Neuen Link anfordern" 910 + 911 + #: letters/templates/letters/password_reset_done.html:4 912 + msgid "Reset email sent" 913 + msgstr "Zurรผcksetzungs-E-Mail gesendet" 914 + 915 + #: letters/templates/letters/password_reset_done.html:9 916 + msgid "" 917 + "If an account exists for that email address, we just sent you instructions " 918 + "to choose a new password." 609 919 msgstr "" 920 + "Falls ein Konto fรผr diese E-Mail-Adresse existiert, haben wir Ihnen gerade Anweisungen " 921 + "zum Festlegen eines neuen Passworts gesendet." 610 922 611 - #: letters/templates/letters/partials/suggestions.html:101 612 - msgid "Related Keywords" 613 - msgstr "Verwandte Schlagwรถrter" 923 + #: letters/templates/letters/password_reset_done.html:10 924 + msgid "The link will stay valid for a limited time." 925 + msgstr "Der Link bleibt fรผr eine begrenzte Zeit gรผltig." 614 926 615 - #: letters/templates/letters/partials/suggestions.html:115 616 - msgid "Similar Letters" 617 - msgstr "ร„hnliche Briefe" 927 + #: letters/templates/letters/password_reset_done.html:11 928 + msgid "Back to login" 929 + msgstr "Zurรผck zur Anmeldung" 618 930 619 - #: letters/templates/letters/partials/suggestions.html:118 620 - msgid "Others have written about similar topics:" 621 - msgstr "Andere haben รผber รคhnliche Themen geschrieben:" 931 + #: letters/templates/letters/password_reset_form.html:4 932 + msgid "Reset password" 933 + msgstr "Passwort zurรผcksetzen" 622 934 623 - #: letters/templates/letters/partials/suggestions.html:129 624 - #, fuzzy 625 - #| msgid "To" 626 - msgid "To:" 627 - msgstr "An" 935 + #: letters/templates/letters/password_reset_form.html:8 936 + msgid "Reset your password" 937 + msgstr "Setzen Sie Ihr Passwort zurรผck" 628 938 629 - #: letters/templates/letters/partials/suggestions.html:131 630 - msgid "by" 939 + #: letters/templates/letters/password_reset_form.html:9 940 + msgid "" 941 + "Enter the email address you used during registration. We will send you a " 942 + "link to create a new password." 631 943 msgstr "" 944 + "Geben Sie die E-Mail-Adresse ein, die Sie bei der Registrierung verwendet haben. Wir " 945 + "senden Ihnen einen Link zum Erstellen eines neuen Passworts." 632 946 633 - #: letters/templates/letters/partials/suggestions.html:163 634 - msgid "Selected:" 635 - msgstr "Ausgewรคhlt:" 947 + #: letters/templates/letters/password_reset_form.html:17 948 + msgid "Send reset link" 949 + msgstr "Zurรผcksetzungslink senden" 636 950 637 951 # Profile page 638 - #: letters/templates/letters/profile.html:8 952 + #: letters/templates/letters/profile.html:13 639 953 #, python-format 640 954 msgid "%(username)s's Profile" 641 955 msgstr "Profil von %(username)s" 642 956 643 - #: letters/templates/letters/profile.html:11 644 - msgid "Identity Verification" 645 - msgstr "Identitรคtsverifizierung" 957 + #: letters/templates/letters/profile.html:16 958 + msgid "Identity & Constituency" 959 + msgstr "Identitรคt & Wahlkreis" 646 960 647 - #: letters/templates/letters/profile.html:15 648 - msgid "Identity Verified" 649 - msgstr "Identitรคt verifiziert" 650 - 651 - #: letters/templates/letters/profile.html:17 652 - msgid "Your signatures will be marked as verified constituent signatures." 653 - msgstr "" 654 - "Ihre Unterschriften werden als verifizierte Wรคhlerunterschriften markiert." 961 + #: letters/templates/letters/profile.html:19 962 + msgid "Status:" 963 + msgstr "Status:" 655 964 656 965 #: letters/templates/letters/profile.html:21 657 - #: letters/templates/letters/representative_detail.html:23 658 - msgid "Parliament:" 966 + msgid "Type:" 967 + msgstr "Typ:" 968 + 969 + #: letters/templates/letters/profile.html:28 970 + msgid "" 971 + "You self-declared your constituency. Representatives will see your " 972 + "signatures as self-declared constituents." 659 973 msgstr "" 974 + "Sie haben Ihren Wahlkreis selbst angegeben. Abgeordnete werden Ihre Unterschriften als " 975 + "selbst angegebene Wรคhler sehen." 660 976 661 - #: letters/templates/letters/profile.html:27 662 - msgid "Verification Pending" 663 - msgstr "Verifizierung ausstehend" 977 + #: letters/templates/letters/profile.html:30 978 + msgid "Start third-party verification" 979 + msgstr "Drittanbieter-Verifizierung starten" 980 + 981 + #: letters/templates/letters/profile.html:33 982 + msgid "" 983 + "Your identity was verified via a third-party provider. Signatures will " 984 + "appear as verified constituents." 985 + msgstr "" 986 + "Ihre Identitรคt wurde รผber einen Drittanbieter verifiziert. Unterschriften werden als " 987 + "verifizierte Wรคhler angezeigt." 664 988 665 - #: letters/templates/letters/profile.html:29 989 + #: letters/templates/letters/profile.html:38 666 990 msgid "Your verification is being processed." 667 991 msgstr "Ihre Verifizierung wird bearbeitet." 668 992 669 - #: letters/templates/letters/profile.html:31 993 + #: letters/templates/letters/profile.html:39 670 994 msgid "Complete Verification (Stub)" 671 995 msgstr "Verifizierung abschlieรŸen (Stub)" 672 996 673 - #: letters/templates/letters/profile.html:35 674 - msgid "Verification Failed" 675 - msgstr "Verifizierung fehlgeschlagen" 997 + #: letters/templates/letters/profile.html:43 998 + msgid "Verification failed. Please try again or contact support." 999 + msgstr "Verifizierung fehlgeschlagen. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support." 676 1000 677 - #: letters/templates/letters/profile.html:37 678 - msgid "Please try again or contact support." 679 - msgstr "Bitte versuchen Sie es erneut oder kontaktieren Sie den Support." 1001 + #: letters/templates/letters/profile.html:48 1002 + msgid "" 1003 + "You can self-declare your constituency below or start a verification with a " 1004 + "trusted provider. Verified signatures carry more weight." 1005 + msgstr "" 1006 + "Sie kรถnnen Ihren Wahlkreis unten selbst angeben oder eine Verifizierung mit einem " 1007 + "vertrauenswรผrdigen Anbieter starten. Verifizierte Unterschriften haben mehr Gewicht." 680 1008 681 - #: letters/templates/letters/profile.html:43 1009 + #: letters/templates/letters/profile.html:50 1010 + msgid "Start Third-party Verification" 1011 + msgstr "Drittanbieter-Verifizierung starten" 1012 + 1013 + #: letters/templates/letters/profile.html:55 1014 + msgid "Ihre Adresse" 1015 + msgstr "Ihre Adresse" 1016 + 1017 + #: letters/templates/letters/profile.html:57 682 1018 msgid "" 683 - "Verify your identity to prove you're a constituent of the representatives " 684 - "you write to. Verified signatures carry more weight!" 1019 + "Geben Sie Ihre vollstรคndige Adresse ein, um prรคzise Wahlkreis- und " 1020 + "Abgeordnetenempfehlungen zu erhalten." 685 1021 msgstr "" 686 - "Verifizieren Sie Ihre Identitรคt, um zu beweisen, dass Sie Wรคhler der " 687 - "Abgeordneten sind, an die Sie schreiben. Verifizierte Unterschriften haben " 688 - "mehr Gewicht!" 1022 + "Geben Sie Ihre vollstรคndige Adresse ein, um prรคzise Wahlkreis- und " 1023 + "Abgeordnetenempfehlungen zu erhalten." 689 1024 690 - #: letters/templates/letters/profile.html:45 691 - msgid "Start Verification" 692 - msgstr "Verifizierung starten" 1025 + #: letters/templates/letters/profile.html:61 1026 + msgid "Gespeicherte Adresse:" 1027 + msgstr "Gespeicherte Adresse:" 1028 + 1029 + #: letters/templates/letters/profile.html:83 1030 + msgid "Adresse speichern" 1031 + msgstr "Adresse speichern" 693 1032 694 - #: letters/templates/letters/profile.html:51 1033 + #: letters/templates/letters/profile.html:88 1034 + msgid "Self-declare your constituency" 1035 + msgstr "Geben Sie Ihren Wahlkreis selbst an" 1036 + 1037 + #: letters/templates/letters/profile.html:90 1038 + msgid "" 1039 + "Select the constituencies you live in so we can prioritise the right " 1040 + "representatives." 1041 + msgstr "" 1042 + "Wรคhlen Sie die Wahlkreise aus, in denen Sie leben, damit wir die richtigen " 1043 + "Abgeordneten priorisieren kรถnnen." 1044 + 1045 + #: letters/templates/letters/profile.html:109 1046 + msgid "Save constituencies" 1047 + msgstr "Wahlkreise speichern" 1048 + 1049 + #: letters/templates/letters/profile.html:114 695 1050 msgid "Your Letters" 696 1051 msgstr "Ihre Briefe" 697 1052 698 - #: letters/templates/letters/profile.html:57 1053 + #: letters/templates/letters/profile.html:120 699 1054 msgid "You haven't written any letters yet." 700 1055 msgstr "Sie haben noch keine Briefe geschrieben." 701 1056 702 - #: letters/templates/letters/profile.html:57 1057 + #: letters/templates/letters/profile.html:120 703 1058 msgid "Write one now!" 704 1059 msgstr "Schreiben Sie jetzt einen!" 705 1060 706 - #: letters/templates/letters/profile.html:62 1061 + #: letters/templates/letters/profile.html:125 707 1062 msgid "Letters You've Signed" 708 1063 msgstr "Briefe, die Sie unterzeichnet haben" 709 1064 710 - # Letter detail page 711 - #: letters/templates/letters/profile.html:68 712 - msgid "Signed on" 713 - msgstr "Unterzeichnet am" 714 - 715 - #: letters/templates/letters/profile.html:71 716 - msgid "Your comment:" 717 - msgstr "Ihr Kommentar:" 718 - 719 - #: letters/templates/letters/profile.html:76 1065 + #: letters/templates/letters/profile.html:133 720 1066 msgid "You haven't signed any letters yet." 721 1067 msgstr "Sie haben noch keine Briefe unterzeichnet." 722 1068 723 - #: letters/templates/letters/profile.html:76 1069 + #: letters/templates/letters/profile.html:133 724 1070 msgid "Browse letters" 725 1071 msgstr "Briefe durchsuchen" 726 1072 727 - #: letters/templates/letters/register.html:22 728 - #: letters/templates/letters/representative_detail.html:56 1073 + #: letters/templates/letters/profile.html:138 1074 + msgid "Account" 1075 + msgstr "Konto" 1076 + 1077 + #: letters/templates/letters/profile.html:140 1078 + msgid "" 1079 + "Need a fresh start? You can delete your account at any time. Your letters " 1080 + "stay visible but without your name." 1081 + msgstr "" 1082 + "Brauchen Sie einen Neuanfang? Sie kรถnnen Ihr Konto jederzeit lรถschen. Ihre Briefe " 1083 + "bleiben sichtbar, aber ohne Ihren Namen." 1084 + 1085 + #: letters/templates/letters/register.html:9 1086 + msgid "" 1087 + "After registration we'll send you an email to confirm your address before " 1088 + "you can sign in." 1089 + msgstr "" 1090 + "Nach der Registrierung senden wir Ihnen eine E-Mail, um Ihre Adresse zu bestรคtigen, " 1091 + "bevor Sie sich anmelden kรถnnen." 1092 + 1093 + #: letters/templates/letters/register.html:23 729 1094 msgid "Email:" 730 1095 msgstr "E-Mail:" 731 1096 732 1097 # Register page 733 - #: letters/templates/letters/register.html:30 1098 + #: letters/templates/letters/register.html:31 734 1099 msgid "First Name (optional):" 735 1100 msgstr "Vorname (optional):" 736 1101 737 - #: letters/templates/letters/register.html:38 1102 + #: letters/templates/letters/register.html:39 738 1103 msgid "Last Name (optional):" 739 1104 msgstr "Nachname (optional):" 740 1105 741 - #: letters/templates/letters/register.html:54 1106 + #: letters/templates/letters/register.html:55 742 1107 msgid "Confirm Password:" 743 1108 msgstr "Passwort bestรคtigen:" 744 1109 745 - #: letters/templates/letters/register.html:69 1110 + #: letters/templates/letters/register.html:70 746 1111 msgid "Already have an account?" 747 1112 msgstr "Bereits ein Konto?" 748 1113 749 - #: letters/templates/letters/register.html:69 1114 + #: letters/templates/letters/register.html:70 750 1115 msgid "Login here" 751 1116 msgstr "Hier anmelden" 752 1117 753 - # Representative detail 754 1118 #: letters/templates/letters/representative_detail.html:19 755 - msgid "Party:" 756 - msgstr "Partei:" 757 - 758 - #: letters/templates/letters/representative_detail.html:27 759 - msgid "Legislative Body:" 760 - msgstr "Parlament:" 761 - 762 - #: letters/templates/letters/representative_detail.html:32 763 - msgid "Role:" 764 - msgstr "Rolle:" 765 - 766 - #: letters/templates/letters/representative_detail.html:38 767 - msgid "Term:" 768 - msgstr "Amtszeit:" 769 - 770 - #: letters/templates/letters/representative_detail.html:43 771 - msgid "Present" 772 - msgstr "Heute" 773 - 774 - #: letters/templates/letters/representative_detail.html:47 775 - msgid "Status:" 776 - msgstr "Status:" 777 - 778 - #: letters/templates/letters/representative_detail.html:49 779 - #: letters/templates/letters/representative_detail.html:98 780 - msgid "Active" 781 - msgstr "Aktiv" 1119 + msgid "รœber" 1120 + msgstr "รœber" 782 1121 783 - #: letters/templates/letters/representative_detail.html:51 784 - msgid "Inactive" 785 - msgstr "Inaktiv" 786 - 787 - #: letters/templates/letters/representative_detail.html:62 788 - msgid "Website:" 789 - msgstr "Webseite:" 790 - 791 - #: letters/templates/letters/representative_detail.html:75 1122 + #: letters/templates/letters/representative_detail.html:30 792 1123 msgid "Committee Memberships" 793 1124 msgstr "Ausschussmitgliedschaften" 794 1125 795 - #: letters/templates/letters/representative_detail.html:113 796 - msgid "Policy Competences" 797 - msgstr "Politische Kompetenzen" 798 - 799 - #: letters/templates/letters/representative_detail.html:117 800 - msgid "" 801 - "Based on committee memberships, this representative works on the following " 802 - "policy areas:" 803 - msgstr "" 804 - "Basierend auf Ausschussmitgliedschaften arbeitet dieser Abgeordnete in " 805 - "folgenden Politikbereichen:" 806 - 807 - #: letters/templates/letters/representative_detail.html:134 1126 + #: letters/templates/letters/representative_detail.html:75 808 1127 msgid "Open Letters" 809 1128 msgstr "Offene Briefe" 810 1129 811 - #: letters/templates/letters/representative_detail.html:142 1130 + #: letters/templates/letters/representative_detail.html:83 812 1131 msgid "No letters have been written to this representative yet." 813 1132 msgstr "An diesen Abgeordneten wurden noch keine Briefe geschrieben." 814 1133 815 - #: letters/templates/letters/representative_detail.html:144 1134 + #: letters/templates/letters/representative_detail.html:85 816 1135 msgid "Write the First Letter" 817 1136 msgstr "Ersten Brief schreiben" 818 1137 819 - #: letters/templates/letters/representative_detail.html:156 1138 + #: letters/templates/letters/representative_detail.html:95 820 1139 msgid "External Resources" 821 1140 msgstr "Externe Ressourcen" 822 1141 823 - #: letters/templates/letters/representative_detail.html:161 1142 + #: letters/templates/letters/representative_detail.html:100 824 1143 msgid "Abgeordnetenwatch Profile" 825 1144 msgstr "Abgeordnetenwatch-Profil" 826 1145 827 - #: letters/templates/letters/representative_detail.html:163 1146 + #: letters/templates/letters/representative_detail.html:102 828 1147 msgid "" 829 1148 "View voting record, questions, and detailed profile on Abgeordnetenwatch.de" 830 1149 msgstr "" 831 1150 "Abstimmungsverhalten, Fragen und detailliertes Profil auf Abgeordnetenwatch." 832 1151 "de ansehen" 833 1152 834 - #: letters/templates/letters/representative_detail.html:166 835 - msgid "View on Abgeordnetenwatch" 836 - msgstr "Auf Abgeordnetenwatch ansehen" 1153 + #: letters/templates/letters/representative_detail.html:112 1154 + msgid "Wikipedia Article" 1155 + msgstr "Wikipedia-Artikel" 837 1156 838 - #: letters/templates/letters/representative_detail.html:173 839 - msgid "Wikipedia Article (German)" 840 - msgstr "Wikipedia-Artikel (Deutsch)" 841 - 842 - #: letters/templates/letters/representative_detail.html:175 1157 + #: letters/templates/letters/representative_detail.html:114 843 1158 msgid "Read more about this representative on Wikipedia" 844 1159 msgstr "Mehr รผber diesen Abgeordneten auf Wikipedia lesen" 845 1160 846 - #: letters/templates/letters/representative_detail.html:178 1161 + #: letters/templates/letters/representative_detail.html:117 847 1162 msgid "View on Wikipedia" 848 1163 msgstr "Auf Wikipedia ansehen" 849 1164 850 - #: letters/templates/letters/representative_detail.html:184 1165 + #: letters/templates/letters/representative_detail.html:123 851 1166 msgid "No external resources available for this representative." 852 1167 msgstr "Keine externen Ressourcen fรผr diesen Abgeordneten verfรผgbar." 853 1168 854 - #: letters/templates/letters/representative_detail.html:192 1169 + #: letters/templates/letters/representative_detail.html:130 1170 + msgid "Kontakt" 1171 + msgstr "Kontakt" 1172 + 1173 + #: letters/templates/letters/representative_detail.html:145 855 1174 #, python-format 856 1175 msgid "Start a new open letter to %(name)s" 857 1176 msgstr "Einen neuen offenen Brief an %(name)s beginnen" 858 1177 859 - #: letters/templates/letters/representative_detail.html:200 1178 + #: letters/templates/letters/representative_detail.html:153 860 1179 msgid "Login to Write Letter" 861 1180 msgstr "Anmelden um Brief zu schreiben" 862 1181 1182 + #: letters/views.py:52 1183 + msgid "Confirm your WriteThem.eu account" 1184 + msgstr "Bestรคtigen Sie Ihr WriteThem.eu-Konto" 1185 + 863 1186 # Flash messages 864 - #: letters/views.py:158 1187 + #: letters/views.py:184 865 1188 msgid "Your letter has been published and your signature has been added!" 866 - msgstr "" 867 - "Ihr Brief wurde verรถffentlicht und Ihre Unterschrift wurde hinzugefรผgt!" 1189 + msgstr "Ihr Brief wurde verรถffentlicht und Ihre Unterschrift wurde hinzugefรผgt!" 868 1190 869 - #: letters/views.py:171 1191 + #: letters/views.py:197 870 1192 msgid "You have already signed this letter." 871 1193 msgstr "Sie haben diesen Brief bereits unterzeichnet." 872 1194 873 - #: letters/views.py:181 1195 + #: letters/views.py:207 874 1196 msgid "Your signature has been added!" 875 1197 msgstr "Ihre Unterschrift wurde hinzugefรผgt!" 876 1198 877 - #: letters/views.py:199 1199 + #: letters/views.py:225 878 1200 msgid "Thank you for your report. Our team will review it." 879 1201 msgstr "Vielen Dank fรผr Ihre Meldung. Unser Team wird sie รผberprรผfen." 880 1202 881 - #: letters/views.py:226 882 - #, python-format 883 - msgid "Welcome, %(username)s! Your account has been created." 884 - msgstr "Willkommen, %(username)s! Ihr Konto wurde erstellt." 1203 + #: letters/views.py:259 1204 + msgid "" 1205 + "Please confirm your email address. We sent you a link to activate your " 1206 + "account." 1207 + msgstr "" 1208 + "Bitte bestรคtigen Sie Ihre E-Mail-Adresse. Wir haben Ihnen einen Link zum Aktivieren " 1209 + "Ihres Kontos gesendet." 885 1210 886 - #~ msgid "Previous" 887 - #~ msgstr "Zurรผck" 1211 + #: letters/views.py:289 1212 + msgid "Your account has been activated. You can now log in." 1213 + msgstr "Ihr Konto wurde aktiviert. Sie kรถnnen sich jetzt anmelden." 1214 + 1215 + #: letters/views.py:292 1216 + msgid "Your account is already active." 1217 + msgstr "Ihr Konto ist bereits aktiv." 888 1218 889 - #~ msgid "Next" 890 - #~ msgstr "Weiter" 1219 + #: letters/views.py:347 1220 + msgid "Ihre Adresse wurde gespeichert." 1221 + msgstr "Ihre Adresse wurde gespeichert." 1222 + 1223 + #: letters/views.py:363 1224 + msgid "Your constituency information has been updated." 1225 + msgstr "Ihre Wahlkreisinformationen wurden aktualisiert." 1226 + 1227 + #: letters/views.py:391 1228 + msgid "" 1229 + "Your account has been deleted. Your published letters remain available to " 1230 + "the public." 1231 + msgstr "" 1232 + "Ihr Konto wurde gelรถscht. Ihre verรถffentlichten Briefe bleiben fรผr die " 1233 + "ร–ffentlichkeit verfรผgbar." 1234 + 1235 + # Forms 1236 + #~ msgid "Postal code (PLZ)" 1237 + #~ msgstr "Postleitzahl (PLZ)" 1238 + 1239 + #, fuzzy 1240 + #~| msgid "Use your PLZ to narrow down representatives from your constituency." 1241 + #~ msgid "Use your PLZ to narrow down representatives from your parliament." 1242 + #~ msgstr "" 1243 + #~ "Verwenden Sie Ihre PLZ, um Abgeordnete aus Ihrem Wahlkreis einzugrenzen." 1244 + 1245 + #~ msgid "Comma-separated tags (e.g., \"climate, transport, education\")" 1246 + #~ msgstr "Komma-getrennte Schlagwรถrter (z.B. \"Klima, Verkehr, Bildung\")" 1247 + 1248 + #~ msgid "climate, transport, education" 1249 + #~ msgstr "Klima, Verkehr, Bildung" 1250 + 1251 + #~ msgid "Write your letter here" 1252 + #~ msgstr "Schreiben Sie hier Ihren Brief" 1253 + 1254 + #, fuzzy 1255 + #~| msgid "Federal" 1256 + #~ msgid "Federal district" 1257 + #~ msgstr "Bund" 1258 + 1259 + #~ msgid "Your postal code (PLZ):" 1260 + #~ msgstr "Ihre Postleitzahl (PLZ):" 1261 + 1262 + #~ msgid "Or select from suggestions on the right โ†’" 1263 + #~ msgstr "Oder wรคhlen Sie aus den Vorschlรคgen rechts โ†’" 1264 + 1265 + #~ msgid "Tags (optional):" 1266 + #~ msgstr "Schlagwรถrter (optional):" 891 1267 892 1268 #, python-format 893 - #~ msgid "Page %(page)s of %(total)s" 894 - #~ msgstr "Seite %(page)s von %(total)s" 1269 + #~ msgid "" 1270 + #~ "Based on the topic \"%(topic)s\", we suggest contacting representatives " 1271 + #~ "from the %(level)s:" 1272 + #~ msgstr "" 1273 + #~ "Basierend auf dem Thema \"%(topic)s\" empfehlen wir, Abgeordnete des " 1274 + #~ "%(level)s zu kontaktieren:" 1275 + 1276 + #, fuzzy 1277 + #~| msgid "Suggested Representatives" 1278 + #~ msgid "Suggested Tags" 1279 + #~ msgstr "Vorgeschlagene Abgeordnete" 1280 + 1281 + #~ msgid "Similar Letters" 1282 + #~ msgstr "ร„hnliche Briefe" 1283 + 1284 + #~ msgid "Others have written about similar topics:" 1285 + #~ msgstr "Andere haben รผber รคhnliche Themen geschrieben:" 895 1286 896 - #~ msgid "No letters found." 897 - #~ msgstr "Keine Briefe gefunden." 1287 + #, fuzzy 1288 + #~| msgid "To" 1289 + #~ msgid "To:" 1290 + #~ msgstr "An" 1291 + 1292 + #~ msgid "Identity Verification" 1293 + #~ msgstr "Identitรคtsverifizierung" 898 1294 899 - #~ msgid "Be the first to write one!" 900 - #~ msgstr "Schreiben Sie den ersten!" 1295 + #~ msgid "Your signatures will be marked as verified constituent signatures." 1296 + #~ msgstr "" 1297 + #~ "Ihre Unterschriften werden als verifizierte Wรคhlerunterschriften markiert." 1298 + 1299 + #~ msgid "Verification Pending" 1300 + #~ msgstr "Verifizierung ausstehend" 1301 + 1302 + #~ msgid "Verification Failed" 1303 + #~ msgstr "Verifizierung fehlgeschlagen" 1304 + 1305 + # Letter detail page 1306 + #~ msgid "Signed on" 1307 + #~ msgstr "Unterzeichnet am" 1308 + 1309 + #~ msgid "Your comment:" 1310 + #~ msgstr "Ihr Kommentar:" 1311 + 1312 + # Representative detail 1313 + #~ msgid "Party:" 1314 + #~ msgstr "Partei:" 1315 + 1316 + #~ msgid "Legislative Body:" 1317 + #~ msgstr "Parlament:" 1318 + 1319 + #~ msgid "Present" 1320 + #~ msgstr "Heute" 1321 + 1322 + #~ msgid "Inactive" 1323 + #~ msgstr "Inaktiv" 1324 + 1325 + #~ msgid "" 1326 + #~ "Based on committee memberships, this representative works on the " 1327 + #~ "following policy areas:" 1328 + #~ msgstr "" 1329 + #~ "Basierend auf Ausschussmitgliedschaften arbeitet dieser Abgeordnete in " 1330 + #~ "folgenden Politikbereichen:" 1331 + 1332 + #, python-format 1333 + #~ msgid "Welcome, %(username)s! Your account has been created." 1334 + #~ msgstr "Willkommen, %(username)s! Ihr Konto wurde erstellt." 901 1335 902 1336 #, python-format 903 1337 #~ msgid "<strong>%(counter)s</strong> other verified" ··· 915 1349 916 1350 #~ msgid "Committee Memberships:" 917 1351 #~ msgstr "Ausschussmitgliedschaften:" 918 - 919 - #~ msgid "Policy Areas:" 920 - #~ msgstr "Politikbereiche:" 921 1352 922 1353 # Services explanations 923 1354 #~ msgid "No matching policy areas found. Please try different keywords."
website/locale/en/LC_MESSAGES/.gitkeep

This is a binary file and will not be displayed.

+1218
website/locale/en/LC_MESSAGES/django.po
··· 1 + # SOME DESCRIPTIVE TITLE. 2 + # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 + # This file is distributed under the same license as the PACKAGE package. 4 + # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. 5 + # 6 + #, fuzzy 7 + msgid "" 8 + msgstr "" 9 + "Project-Id-Version: PACKAGE VERSION\n" 10 + "Report-Msgid-Bugs-To: \n" 11 + "POT-Creation-Date: 2025-10-15 00:28+0200\n" 12 + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 + "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 14 + "Language-Team: LANGUAGE <LL@li.org>\n" 15 + "Language: \n" 16 + "MIME-Version: 1.0\n" 17 + "Content-Type: text/plain; charset=UTF-8\n" 18 + "Content-Transfer-Encoding: 8bit\n" 19 + "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 + 21 + #: letters/admin.py:75 22 + msgid "Mandate Details" 23 + msgstr "Mandate Details" 24 + 25 + #: letters/admin.py:78 26 + msgid "Focus Areas" 27 + msgstr "Focus Areas" 28 + 29 + #: letters/admin.py:81 letters/admin.py:95 30 + msgid "Photo" 31 + msgstr "Photo" 32 + 33 + #: letters/admin.py:85 34 + msgid "Metadata" 35 + msgstr "Metadata" 36 + 37 + #: letters/admin.py:94 38 + msgid "No photo" 39 + msgstr "No photo" 40 + 41 + #: letters/admin.py:167 42 + msgid "Topic Areas" 43 + msgstr "Topic Areas" 44 + 45 + #: letters/forms.py:25 46 + msgid "" 47 + "An account with this email already exists. If you registered before, please " 48 + "check your inbox for the activation link or reset your password." 49 + msgstr "" 50 + "An account with this email already exists. If you registered before, please " 51 + "check your inbox for the activation link or reset your password." 52 + 53 + #: letters/forms.py:37 54 + msgid "Title" 55 + msgstr "Title" 56 + 57 + #: letters/forms.py:38 58 + msgid "Letter Body" 59 + msgstr "Letter Body" 60 + 61 + #: letters/forms.py:39 62 + msgid "To Representative" 63 + msgstr "To Representative" 64 + 65 + #: letters/forms.py:42 66 + msgid "Describe your concern briefly" 67 + msgstr "Describe your concern briefly" 68 + 69 + #: letters/forms.py:43 70 + msgid "" 71 + "Write your letter here. Markdown formatting (e.g. **bold**, _italic_) is " 72 + "supported." 73 + msgstr "" 74 + "Write your letter here. Markdown formatting (e.g. **bold**, _italic_) is " 75 + "supported." 76 + 77 + #: letters/forms.py:46 78 + msgid "Letter title" 79 + msgstr "Letter title" 80 + 81 + #: letters/forms.py:50 82 + msgid "Write your letter here..." 83 + msgstr "Write your letter here..." 84 + 85 + #: letters/forms.py:110 86 + msgid "Comment (optional)" 87 + msgstr "Comment (optional)" 88 + 89 + #: letters/forms.py:113 90 + msgid "Add a personal note to your signature" 91 + msgstr "Add a personal note to your signature" 92 + 93 + #: letters/forms.py:119 94 + msgid "Optional: Add your comment..." 95 + msgstr "Optional: Add your comment..." 96 + 97 + #: letters/forms.py:131 98 + msgid "Reason" 99 + msgstr "Reason" 100 + 101 + #: letters/forms.py:132 102 + msgid "Description" 103 + msgstr "Description" 104 + 105 + #: letters/forms.py:135 106 + msgid "Please provide details about why you are reporting this letter" 107 + msgstr "Please provide details about why you are reporting this letter" 108 + 109 + #: letters/forms.py:142 110 + msgid "Please describe the issue..." 111 + msgstr "Please describe the issue..." 112 + 113 + #: letters/forms.py:152 letters/templates/letters/letter_list.html:19 114 + msgid "Search" 115 + msgstr "Search" 116 + 117 + #: letters/forms.py:155 letters/templates/letters/letter_list.html:18 118 + msgid "Search letters..." 119 + msgstr "Search letters..." 120 + 121 + #: letters/forms.py:161 122 + msgid "Tag" 123 + msgstr "Tag" 124 + 125 + #: letters/forms.py:164 126 + msgid "Filter by tag..." 127 + msgstr "Filter by tag..." 128 + 129 + #: letters/forms.py:180 130 + msgid "Bundestag constituency" 131 + msgstr "Bundestag constituency" 132 + 133 + #: letters/forms.py:181 134 + msgid "Pick your Bundestag direct mandate constituency (Wahlkreis)." 135 + msgstr "Pick your Bundestag direct mandate constituency (Wahlkreis)." 136 + 137 + #: letters/forms.py:182 letters/forms.py:189 138 + msgid "Select constituency" 139 + msgstr "Select constituency" 140 + 141 + #: letters/forms.py:187 142 + msgid "State parliament constituency" 143 + msgstr "State parliament constituency" 144 + 145 + #: letters/forms.py:188 146 + msgid "Optionally pick your Landtag constituency if applicable." 147 + msgstr "Optionally pick your Landtag constituency if applicable." 148 + 149 + #: letters/forms.py:240 150 + msgid "Please select at least one constituency to save your profile." 151 + msgstr "Please select at least one constituency to save your profile." 152 + 153 + #: letters/forms.py:252 154 + msgid "StraรŸe und Hausnummer" 155 + msgstr "StraรŸe und Hausnummer" 156 + 157 + #: letters/forms.py:255 158 + msgid "z.B. Unter den Linden 77" 159 + msgstr "z.B. Unter den Linden 77" 160 + 161 + #: letters/forms.py:261 162 + msgid "Postleitzahl" 163 + msgstr "Postleitzahl" 164 + 165 + #: letters/forms.py:264 166 + msgid "z.B. 10117" 167 + msgstr "z.B. 10117" 168 + 169 + #: letters/forms.py:270 170 + msgid "Stadt" 171 + msgstr "Stadt" 172 + 173 + #: letters/forms.py:273 174 + msgid "z.B. Berlin" 175 + msgstr "z.B. Berlin" 176 + 177 + #: letters/forms.py:304 178 + msgid "" 179 + "Bitte geben Sie eine vollstรคndige Adresse ein (StraรŸe, PLZ und Stadt) oder " 180 + "lassen Sie alle Felder leer." 181 + msgstr "" 182 + "Bitte geben Sie eine vollstรคndige Adresse ein (StraรŸe, PLZ und Stadt) oder " 183 + "lassen Sie alle Felder leer." 184 + 185 + #: letters/models.py:15 186 + msgid "European Union" 187 + msgstr "European Union" 188 + 189 + #: letters/models.py:16 190 + msgid "Federal" 191 + msgstr "Federal" 192 + 193 + #: letters/models.py:17 194 + msgid "State" 195 + msgstr "State" 196 + 197 + #: letters/models.py:18 198 + msgid "Local" 199 + msgstr "Local" 200 + 201 + #: letters/models.py:21 202 + msgid "Name of the parliament" 203 + msgstr "Name of the parliament" 204 + 205 + #: letters/models.py:25 206 + msgid "e.g., 'Bundestag', 'Bayerischer Landtag', 'Gemeinderat Mรผnchen'" 207 + msgstr "e.g., 'Bundestag', 'Bayerischer Landtag', 'Gemeinderat Mรผnchen'" 208 + 209 + #: letters/models.py:29 210 + msgid "Geographic identifier (state code, municipality code, etc.)" 211 + msgstr "Geographic identifier (state code, municipality code, etc.)" 212 + 213 + #: letters/models.py:37 214 + msgid "For hierarchical relationships (e.g., local within state)" 215 + msgstr "For hierarchical relationships (e.g., local within state)" 216 + 217 + #: letters/models.py:43 letters/models.py:70 letters/models.py:105 218 + #: letters/models.py:165 letters/models.py:372 letters/models.py:421 219 + msgid "Last time this was synced from external API" 220 + msgstr "Last time this was synced from external API" 221 + 222 + #: letters/models.py:47 letters/templates/letters/committee_detail.html:70 223 + msgid "Parliament" 224 + msgstr "Parliament" 225 + 226 + #: letters/models.py:48 227 + msgid "Parliaments" 228 + msgstr "Parliaments" 229 + 230 + #: letters/models.py:84 231 + msgid "Federal electoral district" 232 + msgstr "Federal electoral district" 233 + 234 + #: letters/models.py:85 235 + msgid "Bundestag state list" 236 + msgstr "Bundestag state list" 237 + 238 + #: letters/models.py:86 239 + msgid "Bundestag federal list" 240 + msgstr "Bundestag federal list" 241 + 242 + #: letters/models.py:87 243 + msgid "State electoral district" 244 + msgstr "State electoral district" 245 + 246 + #: letters/models.py:88 247 + msgid "State regional list" 248 + msgstr "State regional list" 249 + 250 + #: letters/models.py:89 251 + msgid "State wide list" 252 + msgstr "State wide list" 253 + 254 + #: letters/models.py:90 255 + msgid "EU at large" 256 + msgstr "EU at large" 257 + 258 + #: letters/models.py:119 259 + msgid "Direct mandate" 260 + msgstr "Direct mandate" 261 + 262 + #: letters/models.py:120 263 + msgid "State list mandate" 264 + msgstr "State list mandate" 265 + 266 + #: letters/models.py:121 267 + msgid "State regional list mandate" 268 + msgstr "State regional list mandate" 269 + 270 + #: letters/models.py:122 271 + msgid "Federal list mandate" 272 + msgstr "Federal list mandate" 273 + 274 + #: letters/models.py:123 275 + msgid "EU list mandate" 276 + msgstr "EU list mandate" 277 + 278 + #: letters/models.py:444 279 + msgid "Draft" 280 + msgstr "Draft" 281 + 282 + #: letters/models.py:445 283 + msgid "Published" 284 + msgstr "Published" 285 + 286 + #: letters/models.py:446 287 + msgid "Flagged for Review" 288 + msgstr "Flagged for Review" 289 + 290 + #: letters/models.py:447 291 + msgid "Removed" 292 + msgstr "Removed" 293 + 294 + #: letters/models.py:487 295 + msgid "Deleted user" 296 + msgstr "Deleted user" 297 + 298 + #: letters/services.py:2451 299 + #, python-format 300 + msgid "Detected policy area: %(topic)s." 301 + msgstr "Detected policy area: %(topic)s." 302 + 303 + #: letters/services.py:2456 304 + #, python-format 305 + msgid "Prioritising representatives for %(constituencies)s." 306 + msgstr "Prioritising representatives for %(constituencies)s." 307 + 308 + #: letters/services.py:2461 309 + #, python-format 310 + msgid "Filtering by state %(state)s." 311 + msgstr "Filtering by state %(state)s." 312 + 313 + #: letters/services.py:2465 314 + #, python-format 315 + msgid "" 316 + "Postal code %(plz)s had no direct match; showing broader representatives." 317 + msgstr "" 318 + "Postal code %(plz)s had no direct match; showing broader representatives." 319 + 320 + #: letters/services.py:2469 321 + msgid "Showing generally relevant representatives." 322 + msgstr "Showing generally relevant representatives." 323 + 324 + #: letters/templates/letters/account_activation_invalid.html:4 325 + #: letters/templates/letters/account_activation_invalid.html:8 326 + msgid "Activation link invalid" 327 + msgstr "Activation link invalid" 328 + 329 + #: letters/templates/letters/account_activation_invalid.html:10 330 + msgid "" 331 + "We could not verify your activation link. It may have already been used or " 332 + "expired." 333 + msgstr "" 334 + "We could not verify your activation link. It may have already been used or " 335 + "expired." 336 + 337 + #: letters/templates/letters/account_activation_invalid.html:13 338 + msgid "" 339 + "If you still cannot access your account, try registering again or contact " 340 + "support." 341 + msgstr "" 342 + "If you still cannot access your account, try registering again or contact " 343 + "support." 344 + 345 + #: letters/templates/letters/account_activation_invalid.html:15 346 + msgid "Register again" 347 + msgstr "Register again" 348 + 349 + #: letters/templates/letters/account_activation_sent.html:4 350 + msgid "Activate your account" 351 + msgstr "Activate your account" 352 + 353 + #: letters/templates/letters/account_activation_sent.html:8 354 + #: letters/templates/letters/password_reset_done.html:8 355 + msgid "Check your inbox" 356 + msgstr "Check your inbox" 357 + 358 + #: letters/templates/letters/account_activation_sent.html:10 359 + msgid "" 360 + "We sent you an email with a confirmation link. Please click it to activate " 361 + "your account." 362 + msgstr "" 363 + "We sent you an email with a confirmation link. Please click it to activate " 364 + "your account." 365 + 366 + #: letters/templates/letters/account_activation_sent.html:13 367 + msgid "" 368 + "If you do not receive the email within a few minutes, check your spam folder " 369 + "or try registering again." 370 + msgstr "" 371 + "If you do not receive the email within a few minutes, check your spam folder " 372 + "or try registering again." 373 + 374 + #: letters/templates/letters/account_activation_sent.html:15 375 + msgid "Back to homepage" 376 + msgstr "Back to homepage" 377 + 378 + #: letters/templates/letters/account_delete_confirm.html:4 379 + #: letters/templates/letters/profile.html:142 380 + msgid "Delete account" 381 + msgstr "Delete account" 382 + 383 + #: letters/templates/letters/account_delete_confirm.html:8 384 + msgid "Delete your account" 385 + msgstr "Delete your account" 386 + 387 + #: letters/templates/letters/account_delete_confirm.html:10 388 + msgid "" 389 + "Deleting your account will remove your personal data and signatures. Letters " 390 + "you have published stay online but are shown without your name." 391 + msgstr "" 392 + "Deleting your account will remove your personal data and signatures. Letters " 393 + "you have published stay online but are shown without your name." 394 + 395 + #: letters/templates/letters/account_delete_confirm.html:14 396 + msgid "Yes, delete my account" 397 + msgstr "Yes, delete my account" 398 + 399 + #: letters/templates/letters/account_delete_confirm.html:15 400 + #: letters/templates/letters/letter_form.html:61 401 + msgid "Cancel" 402 + msgstr "Cancel" 403 + 404 + #: letters/templates/letters/base.html:140 405 + msgid "Letters" 406 + msgstr "Letters" 407 + 408 + #: letters/templates/letters/base.html:141 409 + msgid "Competencies" 410 + msgstr "Competencies" 411 + 412 + #: letters/templates/letters/base.html:143 413 + #: letters/templates/letters/representative_detail.html:149 414 + msgid "Write Letter" 415 + msgstr "Write Letter" 416 + 417 + #: letters/templates/letters/base.html:144 418 + #: letters/templates/letters/profile.html:4 419 + msgid "Profile" 420 + msgstr "Profile" 421 + 422 + #: letters/templates/letters/base.html:145 423 + msgid "Logout" 424 + msgstr "Logout" 425 + 426 + #: letters/templates/letters/base.html:147 427 + #: letters/templates/letters/letter_detail.html:47 428 + #: letters/templates/letters/letter_detail.html:81 429 + #: letters/templates/letters/login.html:4 430 + #: letters/templates/letters/login.html:8 431 + #: letters/templates/letters/login.html:33 432 + msgid "Login" 433 + msgstr "Login" 434 + 435 + #: letters/templates/letters/base.html:148 436 + #: letters/templates/letters/register.html:4 437 + #: letters/templates/letters/register.html:8 438 + #: letters/templates/letters/register.html:66 439 + msgid "Register" 440 + msgstr "Register" 441 + 442 + #: letters/templates/letters/base.html:151 443 + msgid "Admin" 444 + msgstr "Admin" 445 + 446 + #: letters/templates/letters/base.html:157 447 + msgid "Select language" 448 + msgstr "Select language" 449 + 450 + #: letters/templates/letters/base.html:182 451 + msgid "Empowering citizens to write to their representatives" 452 + msgstr "Empowering citizens to write to their representatives" 453 + 454 + #: letters/templates/letters/committee_detail.html:22 455 + msgid "Related Topics" 456 + msgstr "Related Topics" 457 + 458 + #: letters/templates/letters/committee_detail.html:36 459 + msgid "Members" 460 + msgstr "Members" 461 + 462 + #: letters/templates/letters/committee_detail.html:46 463 + msgid "Role" 464 + msgstr "Role" 465 + 466 + #: letters/templates/letters/committee_detail.html:48 467 + #: letters/templates/letters/representative_detail.html:61 468 + msgid "Active" 469 + msgstr "Active" 470 + 471 + #: letters/templates/letters/committee_detail.html:51 472 + #: letters/templates/letters/partials/representative_card.html:29 473 + msgid "Since" 474 + msgstr "Since" 475 + 476 + #: letters/templates/letters/committee_detail.html:58 477 + msgid "No members recorded for this committee." 478 + msgstr "No members recorded for this committee." 479 + 480 + #: letters/templates/letters/committee_detail.html:67 481 + msgid "Committee Info" 482 + msgstr "Committee Info" 483 + 484 + #: letters/templates/letters/committee_detail.html:71 485 + msgid "Term" 486 + msgstr "Term" 487 + 488 + #: letters/templates/letters/committee_detail.html:74 489 + #: letters/templates/letters/representative_detail.html:105 490 + msgid "View on Abgeordnetenwatch" 491 + msgstr "View on Abgeordnetenwatch" 492 + 493 + #: letters/templates/letters/letter_detail.html:10 494 + #: letters/templates/letters/partials/letter_card.html:5 495 + msgid "By" 496 + msgstr "By" 497 + 498 + #: letters/templates/letters/letter_detail.html:29 499 + #, python-format 500 + msgid "Signatures (%(counter)s)" 501 + msgid_plural "Signatures (%(counter)s)" 502 + msgstr[0] "Signatures (%(counter)s)" 503 + msgstr[1] "Signatures (%(counter)s)" 504 + 505 + #: letters/templates/letters/letter_detail.html:31 506 + #, python-format 507 + msgid "%(counter)s constituent of %(constituency_name)s" 508 + msgid_plural "%(counter)s constituents of %(constituency_name)s" 509 + msgstr[0] "%(counter)s constituent of %(constituency_name)s" 510 + msgstr[1] "%(counter)s constituents of %(constituency_name)s" 511 + 512 + #: letters/templates/letters/letter_detail.html:32 513 + #, python-format 514 + msgid "%(counter)s other verified" 515 + msgid_plural "%(counter)s other verified" 516 + msgstr[0] "%(counter)s other verified" 517 + msgstr[1] "%(counter)s other verified" 518 + 519 + #: letters/templates/letters/letter_detail.html:33 520 + #, python-format 521 + msgid "%(counter)s unverified" 522 + msgid_plural "%(counter)s unverified" 523 + msgstr[0] "%(counter)s unverified" 524 + msgstr[1] "%(counter)s unverified" 525 + 526 + #: letters/templates/letters/letter_detail.html:41 527 + msgid "Sign this letter" 528 + msgstr "Sign this letter" 529 + 530 + #: letters/templates/letters/letter_detail.html:44 531 + msgid "You have signed this letter" 532 + msgstr "You have signed this letter" 533 + 534 + #: letters/templates/letters/letter_detail.html:47 535 + msgid "to sign this letter" 536 + msgstr "to sign this letter" 537 + 538 + #: letters/templates/letters/letter_detail.html:58 539 + msgid "โœ“ Verified Constituent" 540 + msgstr "โœ“ Verified Constituent" 541 + 542 + #: letters/templates/letters/letter_detail.html:60 543 + msgid "โœ“ Verified" 544 + msgstr "โœ“ Verified" 545 + 546 + #: letters/templates/letters/letter_detail.html:70 547 + msgid "No signatures yet. Be the first to sign!" 548 + msgstr "No signatures yet. Be the first to sign!" 549 + 550 + #: letters/templates/letters/letter_detail.html:75 551 + #: letters/templates/letters/letter_detail.html:79 552 + msgid "Report this letter" 553 + msgstr "Report this letter" 554 + 555 + #: letters/templates/letters/letter_detail.html:76 556 + msgid "If you believe this letter violates our guidelines, please report it." 557 + msgstr "If you believe this letter violates our guidelines, please report it." 558 + 559 + #: letters/templates/letters/letter_detail.html:81 560 + msgid "to report this letter" 561 + msgstr "to report this letter" 562 + 563 + #: letters/templates/letters/letter_detail.html:86 564 + msgid "Back to all letters" 565 + msgstr "Back to all letters" 566 + 567 + #: letters/templates/letters/letter_form.html:4 568 + #: letters/templates/letters/representative_detail.html:143 569 + msgid "Write a Letter" 570 + msgstr "Write a Letter" 571 + 572 + #: letters/templates/letters/letter_form.html:10 573 + msgid "Write an Open Letter" 574 + msgstr "Write an Open Letter" 575 + 576 + #: letters/templates/letters/letter_form.html:12 577 + msgid "" 578 + "Write an open letter to a political representative. Your letter will be " 579 + "published publicly so others can read and sign it." 580 + msgstr "" 581 + "Write an open letter to a political representative. Your letter will be " 582 + "published publicly so others can read and sign it." 583 + 584 + #: letters/templates/letters/letter_form.html:16 585 + msgid "Before you write" 586 + msgstr "Before you write" 587 + 588 + #: letters/templates/letters/letter_form.html:18 589 + msgid "Be thoughtful but feel free to be critical." 590 + msgstr "Be thoughtful but feel free to be critical." 591 + 592 + #: letters/templates/letters/letter_form.html:19 593 + msgid "Representatives are humans tooโ€”stay respectful." 594 + msgstr "Representatives are humans tooโ€”stay respectful." 595 + 596 + #: letters/templates/letters/letter_form.html:20 597 + msgid "Keep your arguments clear and concise." 598 + msgstr "Keep your arguments clear and concise." 599 + 600 + #: letters/templates/letters/letter_form.html:21 601 + msgid "No insults or hate speech." 602 + msgstr "No insults or hate speech." 603 + 604 + #: letters/templates/letters/letter_form.html:22 605 + msgid "Stay within the bounds of the Grundgesetz when making demands." 606 + msgstr "Stay within the bounds of the Grundgesetz when making demands." 607 + 608 + #: letters/templates/letters/letter_form.html:30 609 + msgid "Title:" 610 + msgstr "Title:" 611 + 612 + #: letters/templates/letters/letter_form.html:36 613 + msgid "" 614 + "Describe your concern in a sentence; we'll use it to suggest representatives." 615 + msgstr "" 616 + "Describe your concern in a sentence; we'll use it to suggest representatives." 617 + 618 + #: letters/templates/letters/letter_form.html:41 619 + msgid "To Representative:" 620 + msgstr "To Representative:" 621 + 622 + #: letters/templates/letters/letter_form.html:47 623 + msgid "" 624 + "Already know who to address? Pick them here. Otherwise, use the suggestions " 625 + "below." 626 + msgstr "" 627 + "Already know who to address? Pick them here. Otherwise, use the suggestions " 628 + "below." 629 + 630 + #: letters/templates/letters/letter_form.html:53 631 + msgid "Letter Body:" 632 + msgstr "Letter Body:" 633 + 634 + #: letters/templates/letters/letter_form.html:60 635 + msgid "Publish Letter" 636 + msgstr "Publish Letter" 637 + 638 + #: letters/templates/letters/letter_form.html:67 639 + msgid "Smart Suggestions" 640 + msgstr "Smart Suggestions" 641 + 642 + #: letters/templates/letters/letter_form.html:69 643 + msgid "" 644 + "Type your title and we'll use your verified profile to suggest " 645 + "representatives, topics, and related letters." 646 + msgstr "" 647 + "Type your title and we'll use your verified profile to suggest " 648 + "representatives, topics, and related letters." 649 + 650 + #: letters/templates/letters/letter_form.html:81 651 + msgid "Loading..." 652 + msgstr "Loading..." 653 + 654 + #: letters/templates/letters/letter_form.html:83 655 + msgid "Analyzing your title..." 656 + msgstr "Analyzing your title..." 657 + 658 + #: letters/templates/letters/letter_list.html:4 659 + msgid "Browse Letters" 660 + msgstr "Browse Letters" 661 + 662 + #: letters/templates/letters/letter_list.html:8 663 + msgid "About This" 664 + msgstr "About This" 665 + 666 + #: letters/templates/letters/letter_list.html:9 667 + msgid "" 668 + "Make your voice heard, reach out to your representative, participate in " 669 + "democracy." 670 + msgstr "" 671 + "Make your voice heard, reach out to your representative, participate in " 672 + "democracy." 673 + 674 + #: letters/templates/letters/letter_list.html:10 675 + msgid "Open letters authored and signed by fellow citizens." 676 + msgstr "Open letters authored and signed by fellow citizens." 677 + 678 + #: letters/templates/letters/letter_list.html:11 679 + msgid "" 680 + "Physical letters mailed to representatives when number of verified " 681 + "signatures > 100." 682 + msgstr "" 683 + "Physical letters mailed to representatives when number of verified " 684 + "signatures > 100." 685 + 686 + #: letters/templates/letters/letter_list.html:15 687 + msgid "Browse Open Letters" 688 + msgstr "Browse Open Letters" 689 + 690 + #: letters/templates/letters/letter_list.html:25 691 + msgid "Popular tags:" 692 + msgstr "Popular tags:" 693 + 694 + #: letters/templates/letters/letter_list.html:41 695 + msgid "Previous" 696 + msgstr "Previous" 697 + 698 + #: letters/templates/letters/letter_list.html:43 699 + #, python-format 700 + msgid "Page %(page)s of %(total)s" 701 + msgstr "Page %(page)s of %(total)s" 702 + 703 + #: letters/templates/letters/letter_list.html:45 704 + msgid "Next" 705 + msgstr "Next" 706 + 707 + #: letters/templates/letters/letter_list.html:51 708 + msgid "No letters found." 709 + msgstr "No letters found." 710 + 711 + #: letters/templates/letters/letter_list.html:51 712 + msgid "Be the first to write one!" 713 + msgstr "Be the first to write one!" 714 + 715 + #: letters/templates/letters/login.html:14 716 + #: letters/templates/letters/register.html:15 717 + msgid "Username:" 718 + msgstr "Username:" 719 + 720 + #: letters/templates/letters/login.html:22 721 + #: letters/templates/letters/register.html:47 722 + msgid "Password:" 723 + msgstr "Password:" 724 + 725 + #: letters/templates/letters/login.html:37 726 + msgid "Forgot your password?" 727 + msgstr "Forgot your password?" 728 + 729 + #: letters/templates/letters/login.html:41 730 + msgid "Don't have an account?" 731 + msgstr "Don't have an account?" 732 + 733 + #: letters/templates/letters/login.html:41 734 + msgid "Register here" 735 + msgstr "Register here" 736 + 737 + #: letters/templates/letters/partials/letter_card.html:6 738 + msgid "To" 739 + msgstr "To" 740 + 741 + #: letters/templates/letters/partials/letter_card.html:20 742 + #, python-format 743 + msgid "%(counter)s signature" 744 + msgid_plural "%(counter)s signatures" 745 + msgstr[0] "%(counter)s signature" 746 + msgstr[1] "%(counter)s signatures" 747 + 748 + #: letters/templates/letters/partials/letter_card.html:20 749 + #, python-format 750 + msgid "%(counter)s verified" 751 + msgid_plural "%(counter)s verified" 752 + msgstr[0] "%(counter)s verified" 753 + msgstr[1] "%(counter)s verified" 754 + 755 + #: letters/templates/letters/partials/representative_card.html:21 756 + #: letters/templates/letters/partials/representative_card.html:23 757 + msgid "Constituency" 758 + msgstr "Constituency" 759 + 760 + #: letters/templates/letters/partials/representative_card.html:27 761 + msgid "Mandate" 762 + msgstr "Mandate" 763 + 764 + #: letters/templates/letters/partials/representative_card.html:34 765 + msgid "Focus" 766 + msgstr "Focus" 767 + 768 + #: letters/templates/letters/partials/representative_card.html:34 769 + msgid "self-declared" 770 + msgstr "self-declared" 771 + 772 + #: letters/templates/letters/partials/representative_card.html:45 773 + msgid "Committees" 774 + msgstr "Committees" 775 + 776 + #: letters/templates/letters/partials/representative_card.html:57 777 + msgid "Email" 778 + msgstr "Email" 779 + 780 + #: letters/templates/letters/partials/representative_card.html:60 781 + msgid "Website" 782 + msgstr "Website" 783 + 784 + #: letters/templates/letters/partials/representative_card.html:66 785 + msgid "View profile" 786 + msgstr "View profile" 787 + 788 + #: letters/templates/letters/partials/suggestions.html:10 789 + msgid "" 790 + "We couldn't match you to a constituency yet. Update your profile " 791 + "verification to see local representatives." 792 + msgstr "" 793 + "We couldn't match you to a constituency yet. Update your profile " 794 + "verification to see local representatives." 795 + 796 + #: letters/templates/letters/partials/suggestions.html:16 797 + msgid "Our Interpretation" 798 + msgstr "Our Interpretation" 799 + 800 + #: letters/templates/letters/partials/suggestions.html:21 801 + msgid "Topic:" 802 + msgstr "Topic:" 803 + 804 + #: letters/templates/letters/partials/suggestions.html:28 805 + msgid "No specific policy area detected. Try adding more keywords." 806 + msgstr "No specific policy area detected. Try adding more keywords." 807 + 808 + #: letters/templates/letters/partials/suggestions.html:37 809 + msgid "Related Keywords" 810 + msgstr "Related Keywords" 811 + 812 + #: letters/templates/letters/partials/suggestions.html:51 813 + msgid "Your Direct Representatives" 814 + msgstr "Your Direct Representatives" 815 + 816 + #: letters/templates/letters/partials/suggestions.html:56 817 + msgid "These representatives directly represent your constituency:" 818 + msgstr "These representatives directly represent your constituency:" 819 + 820 + #: letters/templates/letters/partials/suggestions.html:67 821 + #: letters/templates/letters/partials/suggestions.html:102 822 + msgid "Select" 823 + msgstr "Select" 824 + 825 + #: letters/templates/letters/partials/suggestions.html:81 826 + msgid "Topic Experts" 827 + msgstr "Topic Experts" 828 + 829 + #: letters/templates/letters/partials/suggestions.html:86 830 + #, python-format 831 + msgid "" 832 + "These representatives are experts on \"%(topic)s\" based on their committee " 833 + "memberships:" 834 + msgstr "" 835 + "These representatives are experts on \"%(topic)s\" based on their committee " 836 + "memberships:" 837 + 838 + #: letters/templates/letters/partials/suggestions.html:95 839 + msgid "of" 840 + msgstr "of" 841 + 842 + #: letters/templates/letters/partials/suggestions.html:116 843 + msgid "Suggested Representatives" 844 + msgstr "Suggested Representatives" 845 + 846 + #: letters/templates/letters/partials/suggestions.html:119 847 + msgid "" 848 + "No representatives found. Representatives may need to be synced for this " 849 + "governmental level." 850 + msgstr "" 851 + "No representatives found. Representatives may need to be synced for this " 852 + "governmental level." 853 + 854 + #: letters/templates/letters/partials/suggestions.html:148 855 + msgid "Selected:" 856 + msgstr "Selected:" 857 + 858 + #: letters/templates/letters/password_reset_complete.html:4 859 + #: letters/templates/letters/password_reset_complete.html:8 860 + msgid "Password updated" 861 + msgstr "Password updated" 862 + 863 + #: letters/templates/letters/password_reset_complete.html:9 864 + msgid "You can now sign in using your new password." 865 + msgstr "You can now sign in using your new password." 866 + 867 + #: letters/templates/letters/password_reset_complete.html:10 868 + msgid "Go to login" 869 + msgstr "Go to login" 870 + 871 + #: letters/templates/letters/password_reset_confirm.html:4 872 + #: letters/templates/letters/password_reset_confirm.html:9 873 + msgid "Choose a new password" 874 + msgstr "Choose a new password" 875 + 876 + #: letters/templates/letters/password_reset_confirm.html:13 877 + msgid "New password" 878 + msgstr "New password" 879 + 880 + #: letters/templates/letters/password_reset_confirm.html:20 881 + msgid "Confirm password" 882 + msgstr "Confirm password" 883 + 884 + #: letters/templates/letters/password_reset_confirm.html:26 885 + msgid "Update password" 886 + msgstr "Update password" 887 + 888 + #: letters/templates/letters/password_reset_confirm.html:29 889 + msgid "Reset link invalid" 890 + msgstr "Reset link invalid" 891 + 892 + #: letters/templates/letters/password_reset_confirm.html:30 893 + msgid "This password reset link is no longer valid. Please request a new one." 894 + msgstr "This password reset link is no longer valid. Please request a new one." 895 + 896 + #: letters/templates/letters/password_reset_confirm.html:31 897 + msgid "Request new link" 898 + msgstr "Request new link" 899 + 900 + #: letters/templates/letters/password_reset_done.html:4 901 + msgid "Reset email sent" 902 + msgstr "Reset email sent" 903 + 904 + #: letters/templates/letters/password_reset_done.html:9 905 + msgid "" 906 + "If an account exists for that email address, we just sent you instructions " 907 + "to choose a new password." 908 + msgstr "" 909 + "If an account exists for that email address, we just sent you instructions " 910 + "to choose a new password." 911 + 912 + #: letters/templates/letters/password_reset_done.html:10 913 + msgid "The link will stay valid for a limited time." 914 + msgstr "The link will stay valid for a limited time." 915 + 916 + #: letters/templates/letters/password_reset_done.html:11 917 + msgid "Back to login" 918 + msgstr "Back to login" 919 + 920 + #: letters/templates/letters/password_reset_form.html:4 921 + msgid "Reset password" 922 + msgstr "Reset password" 923 + 924 + #: letters/templates/letters/password_reset_form.html:8 925 + msgid "Reset your password" 926 + msgstr "Reset your password" 927 + 928 + #: letters/templates/letters/password_reset_form.html:9 929 + msgid "" 930 + "Enter the email address you used during registration. We will send you a " 931 + "link to create a new password." 932 + msgstr "" 933 + "Enter the email address you used during registration. We will send you a " 934 + "link to create a new password." 935 + 936 + #: letters/templates/letters/password_reset_form.html:17 937 + msgid "Send reset link" 938 + msgstr "Send reset link" 939 + 940 + #: letters/templates/letters/profile.html:13 941 + #, python-format 942 + msgid "%(username)s's Profile" 943 + msgstr "%(username)s's Profile" 944 + 945 + #: letters/templates/letters/profile.html:16 946 + msgid "Identity & Constituency" 947 + msgstr "Identity & Constituency" 948 + 949 + #: letters/templates/letters/profile.html:19 950 + msgid "Status:" 951 + msgstr "Status:" 952 + 953 + #: letters/templates/letters/profile.html:21 954 + msgid "Type:" 955 + msgstr "Type:" 956 + 957 + #: letters/templates/letters/profile.html:28 958 + msgid "" 959 + "You self-declared your constituency. Representatives will see your " 960 + "signatures as self-declared constituents." 961 + msgstr "" 962 + "You self-declared your constituency. Representatives will see your " 963 + "signatures as self-declared constituents." 964 + 965 + #: letters/templates/letters/profile.html:30 966 + msgid "Start third-party verification" 967 + msgstr "Start third-party verification" 968 + 969 + #: letters/templates/letters/profile.html:33 970 + msgid "" 971 + "Your identity was verified via a third-party provider. Signatures will " 972 + "appear as verified constituents." 973 + msgstr "" 974 + "Your identity was verified via a third-party provider. Signatures will " 975 + "appear as verified constituents." 976 + 977 + #: letters/templates/letters/profile.html:38 978 + msgid "Your verification is being processed." 979 + msgstr "Your verification is being processed." 980 + 981 + #: letters/templates/letters/profile.html:39 982 + msgid "Complete Verification (Stub)" 983 + msgstr "Complete Verification (Stub)" 984 + 985 + #: letters/templates/letters/profile.html:43 986 + msgid "Verification failed. Please try again or contact support." 987 + msgstr "Verification failed. Please try again or contact support." 988 + 989 + #: letters/templates/letters/profile.html:48 990 + msgid "" 991 + "You can self-declare your constituency below or start a verification with a " 992 + "trusted provider. Verified signatures carry more weight." 993 + msgstr "" 994 + "You can self-declare your constituency below or start a verification with a " 995 + "trusted provider. Verified signatures carry more weight." 996 + 997 + #: letters/templates/letters/profile.html:50 998 + msgid "Start Third-party Verification" 999 + msgstr "Start Third-party Verification" 1000 + 1001 + #: letters/templates/letters/profile.html:55 1002 + msgid "Ihre Adresse" 1003 + msgstr "Ihre Adresse" 1004 + 1005 + #: letters/templates/letters/profile.html:57 1006 + msgid "" 1007 + "Geben Sie Ihre vollstรคndige Adresse ein, um prรคzise Wahlkreis- und " 1008 + "Abgeordnetenempfehlungen zu erhalten." 1009 + msgstr "" 1010 + "Geben Sie Ihre vollstรคndige Adresse ein, um prรคzise Wahlkreis- und " 1011 + "Abgeordnetenempfehlungen zu erhalten." 1012 + 1013 + #: letters/templates/letters/profile.html:61 1014 + msgid "Gespeicherte Adresse:" 1015 + msgstr "Gespeicherte Adresse:" 1016 + 1017 + #: letters/templates/letters/profile.html:83 1018 + msgid "Adresse speichern" 1019 + msgstr "Adresse speichern" 1020 + 1021 + #: letters/templates/letters/profile.html:88 1022 + msgid "Self-declare your constituency" 1023 + msgstr "Self-declare your constituency" 1024 + 1025 + #: letters/templates/letters/profile.html:90 1026 + msgid "" 1027 + "Select the constituencies you live in so we can prioritise the right " 1028 + "representatives." 1029 + msgstr "" 1030 + "Select the constituencies you live in so we can prioritise the right " 1031 + "representatives." 1032 + 1033 + #: letters/templates/letters/profile.html:109 1034 + msgid "Save constituencies" 1035 + msgstr "Save constituencies" 1036 + 1037 + #: letters/templates/letters/profile.html:114 1038 + msgid "Your Letters" 1039 + msgstr "Your Letters" 1040 + 1041 + #: letters/templates/letters/profile.html:120 1042 + msgid "You haven't written any letters yet." 1043 + msgstr "You haven't written any letters yet." 1044 + 1045 + #: letters/templates/letters/profile.html:120 1046 + msgid "Write one now!" 1047 + msgstr "Write one now!" 1048 + 1049 + #: letters/templates/letters/profile.html:125 1050 + msgid "Letters You've Signed" 1051 + msgstr "Letters You've Signed" 1052 + 1053 + #: letters/templates/letters/profile.html:133 1054 + msgid "You haven't signed any letters yet." 1055 + msgstr "You haven't signed any letters yet." 1056 + 1057 + #: letters/templates/letters/profile.html:133 1058 + msgid "Browse letters" 1059 + msgstr "Browse letters" 1060 + 1061 + #: letters/templates/letters/profile.html:138 1062 + msgid "Account" 1063 + msgstr "Account" 1064 + 1065 + #: letters/templates/letters/profile.html:140 1066 + msgid "" 1067 + "Need a fresh start? You can delete your account at any time. Your letters " 1068 + "stay visible but without your name." 1069 + msgstr "" 1070 + "Need a fresh start? You can delete your account at any time. Your letters " 1071 + "stay visible but without your name." 1072 + 1073 + #: letters/templates/letters/register.html:9 1074 + msgid "" 1075 + "After registration we'll send you an email to confirm your address before " 1076 + "you can sign in." 1077 + msgstr "" 1078 + "After registration we'll send you an email to confirm your address before " 1079 + "you can sign in." 1080 + 1081 + #: letters/templates/letters/register.html:23 1082 + msgid "Email:" 1083 + msgstr "Email:" 1084 + 1085 + #: letters/templates/letters/register.html:31 1086 + msgid "First Name (optional):" 1087 + msgstr "First Name (optional):" 1088 + 1089 + #: letters/templates/letters/register.html:39 1090 + msgid "Last Name (optional):" 1091 + msgstr "Last Name (optional):" 1092 + 1093 + #: letters/templates/letters/register.html:55 1094 + msgid "Confirm Password:" 1095 + msgstr "Confirm Password:" 1096 + 1097 + #: letters/templates/letters/register.html:70 1098 + msgid "Already have an account?" 1099 + msgstr "Already have an account?" 1100 + 1101 + #: letters/templates/letters/register.html:70 1102 + msgid "Login here" 1103 + msgstr "Login here" 1104 + 1105 + #: letters/templates/letters/representative_detail.html:19 1106 + msgid "รœber" 1107 + msgstr "รœber" 1108 + 1109 + #: letters/templates/letters/representative_detail.html:30 1110 + msgid "Committee Memberships" 1111 + msgstr "Committee Memberships" 1112 + 1113 + #: letters/templates/letters/representative_detail.html:75 1114 + msgid "Open Letters" 1115 + msgstr "Open Letters" 1116 + 1117 + #: letters/templates/letters/representative_detail.html:83 1118 + msgid "No letters have been written to this representative yet." 1119 + msgstr "No letters have been written to this representative yet." 1120 + 1121 + #: letters/templates/letters/representative_detail.html:85 1122 + msgid "Write the First Letter" 1123 + msgstr "Write the First Letter" 1124 + 1125 + #: letters/templates/letters/representative_detail.html:95 1126 + msgid "External Resources" 1127 + msgstr "External Resources" 1128 + 1129 + #: letters/templates/letters/representative_detail.html:100 1130 + msgid "Abgeordnetenwatch Profile" 1131 + msgstr "Abgeordnetenwatch Profile" 1132 + 1133 + #: letters/templates/letters/representative_detail.html:102 1134 + msgid "" 1135 + "View voting record, questions, and detailed profile on Abgeordnetenwatch.de" 1136 + msgstr "" 1137 + "View voting record, questions, and detailed profile on Abgeordnetenwatch.de" 1138 + 1139 + #: letters/templates/letters/representative_detail.html:112 1140 + msgid "Wikipedia Article" 1141 + msgstr "Wikipedia Article" 1142 + 1143 + #: letters/templates/letters/representative_detail.html:114 1144 + msgid "Read more about this representative on Wikipedia" 1145 + msgstr "Read more about this representative on Wikipedia" 1146 + 1147 + #: letters/templates/letters/representative_detail.html:117 1148 + msgid "View on Wikipedia" 1149 + msgstr "View on Wikipedia" 1150 + 1151 + #: letters/templates/letters/representative_detail.html:123 1152 + msgid "No external resources available for this representative." 1153 + msgstr "No external resources available for this representative." 1154 + 1155 + #: letters/templates/letters/representative_detail.html:130 1156 + msgid "Kontakt" 1157 + msgstr "Kontakt" 1158 + 1159 + #: letters/templates/letters/representative_detail.html:145 1160 + #, python-format 1161 + msgid "Start a new open letter to %(name)s" 1162 + msgstr "Start a new open letter to %(name)s" 1163 + 1164 + #: letters/templates/letters/representative_detail.html:153 1165 + msgid "Login to Write Letter" 1166 + msgstr "Login to Write Letter" 1167 + 1168 + #: letters/views.py:52 1169 + msgid "Confirm your WriteThem.eu account" 1170 + msgstr "Confirm your WriteThem.eu account" 1171 + 1172 + #: letters/views.py:184 1173 + msgid "Your letter has been published and your signature has been added!" 1174 + msgstr "Your letter has been published and your signature has been added!" 1175 + 1176 + #: letters/views.py:197 1177 + msgid "You have already signed this letter." 1178 + msgstr "You have already signed this letter." 1179 + 1180 + #: letters/views.py:207 1181 + msgid "Your signature has been added!" 1182 + msgstr "Your signature has been added!" 1183 + 1184 + #: letters/views.py:225 1185 + msgid "Thank you for your report. Our team will review it." 1186 + msgstr "Thank you for your report. Our team will review it." 1187 + 1188 + #: letters/views.py:259 1189 + msgid "" 1190 + "Please confirm your email address. We sent you a link to activate your " 1191 + "account." 1192 + msgstr "" 1193 + "Please confirm your email address. We sent you a link to activate your " 1194 + "account." 1195 + 1196 + #: letters/views.py:289 1197 + msgid "Your account has been activated. You can now log in." 1198 + msgstr "Your account has been activated. You can now log in." 1199 + 1200 + #: letters/views.py:292 1201 + msgid "Your account is already active." 1202 + msgstr "Your account is already active." 1203 + 1204 + #: letters/views.py:347 1205 + msgid "Ihre Adresse wurde gespeichert." 1206 + msgstr "Ihre Adresse wurde gespeichert." 1207 + 1208 + #: letters/views.py:363 1209 + msgid "Your constituency information has been updated." 1210 + msgstr "Your constituency information has been updated." 1211 + 1212 + #: letters/views.py:391 1213 + msgid "" 1214 + "Your account has been deleted. Your published letters remain available to " 1215 + "the public." 1216 + msgstr "" 1217 + "Your account has been deleted. Your published letters remain available to " 1218 + "the public."
+11 -1
website/writethem/settings.py
··· 43 43 MIDDLEWARE = [ 44 44 'django.middleware.security.SecurityMiddleware', 45 45 'django.contrib.sessions.middleware.SessionMiddleware', 46 + 'django.middleware.locale.LocaleMiddleware', 46 47 'django.middleware.common.CommonMiddleware', 47 48 'django.middleware.csrf.CsrfViewMiddleware', 48 49 'django.contrib.auth.middleware.AuthenticationMiddleware', ··· 105 106 # https://docs.djangoproject.com/en/5.2/topics/i18n/ 106 107 107 108 LANGUAGE_CODE = 'de' 109 + LANGUAGES = [ 110 + ('de', 'Deutsch'), 111 + ('en', 'English'), 112 + ] 108 113 109 114 TIME_ZONE = 'Europe/Berlin' 110 115 111 - USE_I18N = False 116 + USE_I18N = True 112 117 USE_L10N = True 113 118 114 119 USE_TZ = True 120 + 121 + # Locale paths - where Django looks for .po files 122 + LOCALE_PATHS = [ 123 + BASE_DIR / 'locale', 124 + ] 115 125 116 126 117 127 # Static files (CSS, JavaScript, Images)
+12 -3
website/writethem/urls.py
··· 14 14 1. Import the include() function: from django.urls import include, path 15 15 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 16 """ 17 + from django.contrib import admin 18 + from django.urls import path, include 17 19 from django.conf import settings 18 20 from django.conf.urls.static import static 19 - from django.contrib import admin 20 - from django.urls import include, path 21 + from django.conf.urls.i18n import i18n_patterns 22 + from django.views.i18n import set_language 21 23 22 24 urlpatterns = [ 25 + # Language switcher endpoint (no prefix) 26 + path('i18n/setlang/', set_language, name='set_language'), 27 + ] 28 + 29 + # All user-facing URLs get language prefix 30 + urlpatterns += i18n_patterns( 23 31 path('admin/', admin.site.urls), 24 32 path('', include('letters.urls')), 25 - ] 33 + prefix_default_language=True, 34 + ) 26 35 27 36 if settings.DEBUG: 28 37 urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)