wagtail-localize-intentional-blanks 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,340 @@
1
+ """
2
+ Views for handling AJAX requests from the translation editor.
3
+ """
4
+
5
+ import logging
6
+
7
+ from django.contrib.auth.decorators import login_required
8
+ from django.core.exceptions import PermissionDenied
9
+ from django.db.models import Q
10
+ from django.http import JsonResponse
11
+ from django.views.decorators.cache import never_cache
12
+ from django.views.decorators.http import require_POST
13
+ from wagtail_localize.models import (
14
+ StringSegment,
15
+ StringTranslation,
16
+ Translation,
17
+ )
18
+
19
+ from .constants import get_setting
20
+ from .utils import (
21
+ get_backup_separator,
22
+ is_do_not_translate,
23
+ mark_segment_do_not_translate,
24
+ unmark_segment_do_not_translate,
25
+ validate_configuration,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def check_permission(user):
32
+ """
33
+ Check if user has permission to mark segments as "do not translate".
34
+
35
+ Args:
36
+ user: Django User instance
37
+
38
+ Raises:
39
+ PermissionDenied if user doesn't have permission
40
+ """
41
+ required_permission = get_setting("REQUIRED_PERMISSION")
42
+
43
+ if required_permission is None:
44
+ # No specific permission required
45
+ return True
46
+
47
+ if not user.has_perm(required_permission):
48
+ raise PermissionDenied(
49
+ f"User does not have required permission: {required_permission}"
50
+ )
51
+
52
+ return True
53
+
54
+
55
+ @login_required
56
+ @require_POST
57
+ def mark_segment_do_not_translate_view(request, translation_id, segment_id):
58
+ """
59
+ Mark a translation segment as "do not translate".
60
+
61
+ Args:
62
+ translation_id: The Translation ID
63
+ segment_id: The StringSegment ID (not String ID)
64
+
65
+ POST params:
66
+ do_not_translate: bool - True to mark as do not translate, False to unmark
67
+
68
+ Returns:
69
+ JSON response with success status and source value
70
+
71
+ Example AJAX call:
72
+ fetch('/intentional-blanks/translations/123/segment/456/do-not-translate/', {
73
+ method: 'POST',
74
+ headers: {'X-CSRFToken': csrfToken},
75
+ body: new FormData({do_not_translate: 'true'})
76
+ })
77
+ """
78
+ try:
79
+ # Check permissions
80
+ check_permission(request.user)
81
+
82
+ # Get objects
83
+ translation = Translation.objects.get(id=translation_id)
84
+
85
+ # segment_id is the StringSegment ID (matches wagtail-localize's segment.id)
86
+ logger.info(
87
+ f"Marking segment as do not translate: translation_id={translation_id}, segment_id={segment_id}"
88
+ )
89
+
90
+ segment = StringSegment.objects.get(id=segment_id, source=translation.source)
91
+ string = segment.string
92
+
93
+ if not string:
94
+ logger.error(f"StringSegment {segment_id} has no associated String")
95
+ return JsonResponse(
96
+ {
97
+ "success": False,
98
+ "error": f"Segment {segment_id} has no associated String. The translation may be corrupted.",
99
+ },
100
+ status=400,
101
+ )
102
+
103
+ # Get action - only accept explicit 'true' or 'false'
104
+ do_not_translate_param = request.POST.get("do_not_translate", "").lower()
105
+ if do_not_translate_param not in ("true", "false"):
106
+ return JsonResponse(
107
+ {
108
+ "success": False,
109
+ "error": 'Invalid do_not_translate parameter. Must be "true" or "false".',
110
+ },
111
+ status=400,
112
+ )
113
+
114
+ do_not_translate = do_not_translate_param == "true"
115
+
116
+ if do_not_translate:
117
+ mark_segment_do_not_translate(translation, segment, user=request.user)
118
+ message = "Segment marked as do not translate"
119
+ else:
120
+ unmark_segment_do_not_translate(translation, segment)
121
+ message = "Segment unmarked, ready for manual translation"
122
+
123
+ # Get the source text to display in UI
124
+ source_text = segment.string.data if segment.string else ""
125
+
126
+ # Get the translated value (if any) for unmarking
127
+ translated_value = None
128
+ if not do_not_translate:
129
+ try:
130
+ existing_translation = StringTranslation.objects.get(
131
+ translation_of=segment.string,
132
+ locale=translation.target_locale,
133
+ context=segment.context,
134
+ )
135
+ validate_configuration()
136
+ marker = get_setting("MARKER")
137
+ backup_separator = get_backup_separator()
138
+ # Make sure it's not the marker or encoded marker format
139
+ if (
140
+ existing_translation.data != marker
141
+ and not existing_translation.data.startswith(
142
+ marker + backup_separator
143
+ )
144
+ ):
145
+ translated_value = existing_translation.data
146
+ except StringTranslation.DoesNotExist:
147
+ pass
148
+
149
+ return JsonResponse(
150
+ {
151
+ "success": True,
152
+ "source_value": source_text,
153
+ "translated_value": translated_value,
154
+ "do_not_translate": do_not_translate,
155
+ "message": message,
156
+ }
157
+ )
158
+
159
+ except Translation.DoesNotExist:
160
+ return JsonResponse(
161
+ {"success": False, "error": "Translation not found"}, status=404
162
+ )
163
+
164
+ except StringSegment.DoesNotExist:
165
+ logger.error(
166
+ f"StringSegment {segment_id} does not exist for translation {translation_id}"
167
+ )
168
+ return JsonResponse(
169
+ {
170
+ "success": False,
171
+ "error": f"Segment {segment_id} not found. The translation may need to be re-synced.",
172
+ },
173
+ status=404,
174
+ )
175
+
176
+ except PermissionDenied as e:
177
+ return JsonResponse({"success": False, "error": str(e)}, status=403)
178
+
179
+ except Exception as e:
180
+ # Log the error
181
+ logger.exception("Error in mark_segment_do_not_translate_view")
182
+
183
+ return JsonResponse({"success": False, "error": str(e)}, status=400)
184
+
185
+
186
+ @login_required
187
+ def get_segment_status(request, translation_id, segment_id):
188
+ """
189
+ Get the current status of a segment (marked as do not translate or not).
190
+
191
+ Args:
192
+ translation_id: The Translation ID
193
+ segment_id: The StringSegment ID (not String ID)
194
+
195
+ Returns:
196
+ JSON response with status info
197
+
198
+ Example:
199
+ GET /intentional-blanks/translations/123/segment/456/status/
200
+ """
201
+ try:
202
+ check_permission(request.user)
203
+
204
+ translation = Translation.objects.get(id=translation_id)
205
+
206
+ # segment_id is the StringSegment ID (matches wagtail-localize's segment.id)
207
+ segment = StringSegment.objects.get(id=segment_id, source=translation.source)
208
+ string = segment.string
209
+
210
+ if not string:
211
+ return JsonResponse(
212
+ {
213
+ "success": False,
214
+ "error": f"Segment {segment_id} has no associated String.",
215
+ },
216
+ status=400,
217
+ )
218
+
219
+ try:
220
+ string_translation = StringTranslation.objects.get(
221
+ translation_of=segment.string, # translation_of expects a String, not StringSegment
222
+ locale=translation.target_locale,
223
+ )
224
+ do_not_translate = is_do_not_translate(string_translation)
225
+ translated_text = string_translation.data if not do_not_translate else None
226
+ except StringTranslation.DoesNotExist:
227
+ do_not_translate = False
228
+ translated_text = None
229
+
230
+ source_text = segment.string.data if segment.string else ""
231
+
232
+ return JsonResponse(
233
+ {
234
+ "success": True,
235
+ "do_not_translate": do_not_translate,
236
+ "source_text": source_text,
237
+ "translated_text": translated_text,
238
+ }
239
+ )
240
+
241
+ except Translation.DoesNotExist:
242
+ return JsonResponse(
243
+ {"success": False, "error": "Translation not found"}, status=404
244
+ )
245
+
246
+ except StringSegment.DoesNotExist:
247
+ logger.error(
248
+ f"StringSegment {segment_id} does not exist for translation {translation_id}"
249
+ )
250
+ return JsonResponse(
251
+ {
252
+ "success": False,
253
+ "error": f"Segment {segment_id} not found. The translation may need to be re-synced.",
254
+ },
255
+ status=404,
256
+ )
257
+
258
+ except PermissionDenied as e:
259
+ return JsonResponse({"success": False, "error": str(e)}, status=403)
260
+
261
+ except Exception as e:
262
+ return JsonResponse({"success": False, "error": str(e)}, status=400)
263
+
264
+
265
+ @login_required
266
+ @never_cache
267
+ def get_translation_status(request, translation_id):
268
+ """
269
+ Get the status of all segments for a translation in one request.
270
+
271
+ Args:
272
+ translation_id: The Translation ID
273
+
274
+ Returns:
275
+ JSON response with a mapping of StringSegment IDs to their status
276
+
277
+ Example:
278
+ GET /intentional-blanks/translations/123/status/
279
+ Response: {
280
+ "success": true,
281
+ "segments": {
282
+ "456": {"do_not_translate": true, "source_text": "Hello"},
283
+ "457": {"do_not_translate": false, "source_text": "World"}
284
+ }
285
+ }
286
+ (Keys are StringSegment IDs, not String IDs)
287
+ """
288
+ try:
289
+ check_permission(request.user)
290
+
291
+ translation = Translation.objects.get(id=translation_id)
292
+
293
+ # Get all string translations for this translation source that are marked as "do not translate"
294
+ validate_configuration()
295
+ marker = get_setting("MARKER")
296
+ backup_separator = get_backup_separator()
297
+
298
+ # Get all StringSegments for this source
299
+ all_segments = StringSegment.objects.filter(
300
+ source=translation.source
301
+ ).select_related("string")
302
+ string_ids = list(all_segments.values_list("string_id", flat=True))
303
+
304
+ # Get marked translations
305
+ marked_translations = (
306
+ StringTranslation.objects.filter(
307
+ locale=translation.target_locale, translation_of_id__in=string_ids
308
+ )
309
+ .filter(Q(data=marker) | Q(data__startswith=marker + backup_separator))
310
+ .select_related("translation_of")
311
+ )
312
+
313
+ # Build a map: String ID -> StringSegment ID
314
+ string_to_segment_map = {seg.string_id: seg.id for seg in all_segments}
315
+
316
+ # Build a mapping of StringSegment ID -> status
317
+ segments = {}
318
+ for st in marked_translations:
319
+ string_id = st.translation_of.id
320
+ segment_id = string_to_segment_map.get(string_id)
321
+
322
+ if segment_id:
323
+ segments[str(segment_id)] = {
324
+ "do_not_translate": True,
325
+ "source_text": st.translation_of.data,
326
+ }
327
+
328
+ return JsonResponse({"success": True, "segments": segments})
329
+
330
+ except Translation.DoesNotExist:
331
+ return JsonResponse(
332
+ {"success": False, "error": "Translation not found"}, status=404
333
+ )
334
+
335
+ except PermissionDenied as e:
336
+ return JsonResponse({"success": False, "error": str(e)}, status=403)
337
+
338
+ except Exception as e:
339
+ logger.exception("Error in get_translation_status")
340
+ return JsonResponse({"success": False, "error": str(e)}, status=400)
@@ -0,0 +1,8 @@
1
+ """
2
+ Wagtail hooks for intentional blanks integration.
3
+ """
4
+
5
+ # Placeholder for future Wagtail hooks
6
+ # The intentional blanks functionality is now implemented via a monkey-patch
7
+ # to wagtail-localize's TranslationSource._get_segments_for_translation()
8
+ # See patch.py for the implementation.
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: wagtail-localize-intentional-blanks
3
+ Version: 0.1.0
4
+ Summary: Allow translators to mark wagtail-localize segments as 'do not translate'
5
+ Author-email: "Lincoln Loop, LLC" <info@lincolnloop.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/lincolnloop/wagtail-localize-intentional-blanks
8
+ Project-URL: Documentation, https://github.com/lincolnloop/wagtail-localize-intentional-blanks
9
+ Project-URL: Repository, https://github.com/lincolnloop/wagtail-localize-intentional-blanks
10
+ Project-URL: Bug Tracker, https://github.com/lincolnloop/wagtail-localize-intentional-blanks/issues
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: Django
13
+ Classifier: Framework :: Django :: 4.2
14
+ Classifier: Framework :: Django :: 5.0
15
+ Classifier: Framework :: Wagtail
16
+ Classifier: Framework :: Wagtail :: 5
17
+ Classifier: Framework :: Wagtail :: 6
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Software Development :: Internationalization
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: Django>=4.2
31
+ Requires-Dist: wagtail>=5.2
32
+ Requires-Dist: wagtail-localize>=1.8
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0; extra == "dev"
35
+ Requires-Dist: pytest-django>=4.5; extra == "dev"
36
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
37
+ Requires-Dist: selenium>=4.0; extra == "dev"
38
+ Requires-Dist: pillow>=10.0; extra == "dev"
39
+ Requires-Dist: black>=23.0; extra == "dev"
40
+ Requires-Dist: flake8>=6.0; extra == "dev"
41
+ Requires-Dist: isort>=5.12; extra == "dev"
42
+ Requires-Dist: mypy>=1.0; extra == "dev"
43
+ Provides-Extra: docs
44
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
45
+ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
46
+ Dynamic: license-file
47
+
48
+ # wagtail-localize-intentional-blanks
49
+
50
+ A Wagtail library that extends [wagtail-localize](https://github.com/wagtail/wagtail-localize) to allow translators to mark translation segments as "do not translate". These segments count as translated (contributing to progress) but fall back to the source page's value when rendered.
51
+
52
+ <img width="985" height="236" alt="url_intentionally_blank" src="https://github.com/user-attachments/assets/310c5ad0-780c-43a6-a73f-1783c1d5f30e" />
53
+ <img width="1088" height="230" alt="number_intentionally_blank" src="https://github.com/user-attachments/assets/e9be46c7-86cd-43b7-a0d7-1747714c4bf9" />
54
+
55
+
56
+ ## Features
57
+
58
+ - ✅ **Translator Control**: Translators decide which fields to not translate
59
+ - ✅ **Per-Locale Flexibility**: One language can translate a value while another language can use the source value
60
+ - ✅ **Progress Tracking**: "Do not translate" segments count as translated by `wagtail-localize`
61
+ - ✅ **No Template Changes**: Works transparently with existing templates
62
+ - ✅ **Drop-in Integration**: Minimal code changes required
63
+ - ✅ **UI Included**: Adds "Do Not Translate" buttons to translation editor
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install wagtail-localize-intentional-blanks
69
+ ```
70
+
71
+ ## Quick Start
72
+
73
+ ### 1. Add to INSTALLED_APPS
74
+
75
+ **Important:** `wagtail_localize_intentional_blanks` must come **before** `wagtail_localize` in `INSTALLED_APPS` for template overrides to work.
76
+
77
+ ```python
78
+ # settings.py
79
+
80
+ INSTALLED_APPS = [
81
+ # ... other apps
82
+ 'wagtail_localize_intentional_blanks', # Must be BEFORE wagtail_localize
83
+ 'wagtail_localize',
84
+ 'wagtail_localize.locales',
85
+ # ... other apps
86
+ ]
87
+ ```
88
+
89
+ ### 2. Include URLs
90
+
91
+ ```python
92
+ # urls.py
93
+
94
+ from django.urls import path, include
95
+
96
+ urlpatterns = [
97
+ # ... other patterns
98
+ path(
99
+ 'intentional-blanks/', include('wagtail_localize_intentional_blanks.urls')
100
+ ),
101
+ ]
102
+ ```
103
+
104
+ That's it! The "Do Not Translate" button will now appear in the translation editor for all translatable fields. No code changes to your blocks or models required.
105
+
106
+ ## How It Works
107
+
108
+ This library works by:
109
+
110
+ 1. **Adding UI controls** - JavaScript adds "Do Not Translate" checkboxes to the translation editor
111
+ 2. **Storing markers** - When checked, a marker string (`__DO_NOT_TRANSLATE__`) is stored in the translation
112
+ 3. **Automatic replacement** - When rendering pages, a patch intercepts segment retrieval and replaces markers with source values
113
+ 4. **Progress tracking** - Marked segments count as "translated" for progress calculation
114
+
115
+ **Key benefit:** No code changes to your blocks or models. The library handles everything automatically through patching wagtail-localize's internal methods.
116
+
117
+ ## Usage
118
+
119
+ ### In the Translation Editor
120
+
121
+ 1. Open a page translation in wagtail-localize's editor
122
+ 2. For each segment, you'll see a "Mark 'Do Not Translate'" checkbox
123
+ 3. Check it to mark that segment as do not translate
124
+ 4. The segment counts as translated (shows green)
125
+ 5. When the page renders, it automatically shows the source value for that field
126
+ 6. If the value in the original page changes, and the translated pages are synced, the segment still counts as translated (shows green) and when the page renders, it shows the updated source value for that field
127
+
128
+ ### Common Use Cases
129
+
130
+ - **Brand names and trademarks** - Keep consistent across locales
131
+ - **Product codes and SKUs** - No translation needed
132
+ - **URLs** - Pages may contain the translations for different languages, but a URL is the same for all languages
133
+ - **IDs** - Not language-specific identifiers
134
+
135
+ ### Configuration
136
+
137
+ You can customize behavior in your Django settings:
138
+
139
+ ```python
140
+ # settings.py
141
+
142
+ # Enable/disable the feature globally
143
+ WAGTAIL_LOCALIZE_INTENTIONAL_BLANKS_ENABLED = True
144
+
145
+ # Custom marker (advanced)
146
+ WAGTAIL_LOCALIZE_INTENTIONAL_BLANKS_MARKER = "__DO_NOT_TRANSLATE__"
147
+
148
+ # Require specific permission (default: None = any translator)
149
+ WAGTAIL_LOCALIZE_INTENTIONAL_BLANKS_REQUIRED_PERMISSION = 'cms.can_mark_do_not_translate'
150
+ ```
151
+
152
+ ## Advanced Usage
153
+
154
+ ### Programmatic API
155
+
156
+ You can programmatically mark segments as "do not translate" using the provided utilities:
157
+
158
+ ```python
159
+ from wagtail_localize_intentional_blanks.utils import (
160
+ mark_segment_do_not_translate,
161
+ unmark_segment_do_not_translate,
162
+ get_source_fallback_stats,
163
+ )
164
+ from wagtail_localize.models import Translation, StringSegment
165
+
166
+ # Mark a segment
167
+ translation = Translation.objects.get(id=123)
168
+ segment = StringSegment.objects.get(id=456)
169
+ mark_segment_do_not_translate(translation, segment, user=request.user)
170
+
171
+ # Unmark a segment
172
+ unmark_segment_do_not_translate(translation, segment)
173
+
174
+ # Get statistics
175
+ stats = get_source_fallback_stats(translation)
176
+ print(f"{stats['do_not_translate']} segments marked as do not translate")
177
+ print(f"{stats['manually_translated']} segments manually translated")
178
+ ```
179
+
180
+ ## Requirements
181
+
182
+ - Python 3.10+
183
+ - Django 4.2+
184
+ - Wagtail 5.2+
185
+ - wagtail-localize 1.8+
186
+
187
+ ## License
188
+
189
+ MIT License - see [LICENSE](LICENSE) file for details.
190
+
191
+ ## Contributing
192
+
193
+ Contributions are welcome!
194
+
195
+ ## Development
196
+
197
+ ### Setting Up for Development
198
+
199
+ 1. Clone the repository:
200
+ ```bash
201
+ git clone https://github.com/lincolnloop/wagtail-localize-intentional-blanks.git
202
+ cd wagtail-localize-intentional-blanks
203
+ ```
204
+
205
+ 2. Install the package with development dependencies:
206
+ ```bash
207
+ pip install -e ".[dev]"
208
+ ```
209
+
210
+ This installs the package in editable mode along with testing tools (pytest, black, flake8, etc.).
211
+
212
+ ### Running Tests
213
+
214
+ Run the test suite with pytest:
215
+ ```bash
216
+ pytest
217
+ ```
218
+
219
+ Run tests with coverage:
220
+ ```bash
221
+ pytest --cov=wagtail_localize_intentional_blanks
222
+ ```
223
+
224
+ Run specific test files:
225
+ ```bash
226
+ pytest tests/test_utils.py
227
+ pytest tests/test_views.py
228
+ ```
229
+
230
+ ### Code Quality
231
+
232
+ Check code with ruff:
233
+ ```bash
234
+ ruff check .
235
+ ```
236
+
237
+ Format with ruff:
238
+ ```bash
239
+ ruff format .
240
+ ```
241
+
242
+ ## Credits
243
+
244
+ Created by [Lincoln Loop, LLC](https://lincolnloop.com) for the Wagtail community.
@@ -0,0 +1,18 @@
1
+ wagtail_localize_intentional_blanks/__init__.py,sha256=T5-7FLF993ygWsaNKweK7utCRqxJVLwgB2Czi4Lj8_0,857
2
+ wagtail_localize_intentional_blanks/apps.py,sha256=wTgWJ_1whFkExZmUhBEnHj17sbgqwiHVRA2TPzcv1PI,897
3
+ wagtail_localize_intentional_blanks/constants.py,sha256=yhrGeuoe9wmHpqdwztaYQ-7ctz8RsWx64myQU73iKg4,1352
4
+ wagtail_localize_intentional_blanks/patch.py,sha256=e-SpjXvFtyZEOhhj67RvWYvrYgFqbAZTkPOUaHL6VAA,6590
5
+ wagtail_localize_intentional_blanks/urls.py,sha256=KvZ-Jp0ci2qsV6Gc8yajkDYNBlZkCDkqSi4l80bIFN0,685
6
+ wagtail_localize_intentional_blanks/utils.py,sha256=tRCQA3pIRiRFJKRfKcSvQ2K4DLVNahtnPYxnyw3FjGY,14423
7
+ wagtail_localize_intentional_blanks/views.py,sha256=ffvXdve9vl4NwVsmoXj3RBy2W4XOV0IvYVFpaAfqnnI,11244
8
+ wagtail_localize_intentional_blanks/wagtail_hooks.py,sha256=Bk9bpdsquvO_ccbF1bTjpK8q6Yo975fcN_SrrEY1mO0,288
9
+ wagtail_localize_intentional_blanks/static/wagtail_localize_intentional_blanks/css/translation-editor.css,sha256=Iw_3iA35_8EreFaMvFya852RBlUQLiPDPN_8PElxl7k,2363
10
+ wagtail_localize_intentional_blanks/static/wagtail_localize_intentional_blanks/js/translation-editor.js,sha256=JE1g4wDxkWfbc6_FPFI-f2ZgEoOjqrgik4t6dmAxj9s,22753
11
+ wagtail_localize_intentional_blanks/templates/wagtail_localize/admin/edit_translation.html,sha256=ePGwLgWV8__Y3W0LxXAbDv2XCN49l1ZQ__aSkxMbck4,635
12
+ wagtail_localize_intentional_blanks/templatetags/__init__.py,sha256=7wPp_kk5jsll7wxkgUqWBBfHOPDP1tc_D2Cx7gJyH1o,63
13
+ wagtail_localize_intentional_blanks/templatetags/intentional_blanks.py,sha256=66yrni7vY-XPLteVMRcO_aS-Fp0eHuPWe0y3lxV3Bq0,1390
14
+ wagtail_localize_intentional_blanks-0.1.0.dist-info/licenses/LICENSE,sha256=b99OiFRYnxpXDbpBxCVdf-9VJ4htEBKvbky52Jhq08g,1138
15
+ wagtail_localize_intentional_blanks-0.1.0.dist-info/METADATA,sha256=gdwODZ1FJeqRQpz47VAd48otMJDVxe7FpPY0l48rBiQ,7933
16
+ wagtail_localize_intentional_blanks-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ wagtail_localize_intentional_blanks-0.1.0.dist-info/top_level.txt,sha256=H6IKrSKJO-H7vBnwcudF-yPhvlz8A_DD6XcKfioV8dQ,36
18
+ wagtail_localize_intentional_blanks-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,23 @@
1
+ All original code is provided under the MIT License:
2
+
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) 2025 Lincoln Loop, LLC
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ wagtail_localize_intentional_blanks