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.
- wagtail_localize_intentional_blanks/__init__.py +29 -0
- wagtail_localize_intentional_blanks/apps.py +29 -0
- wagtail_localize_intentional_blanks/constants.py +44 -0
- wagtail_localize_intentional_blanks/patch.py +182 -0
- wagtail_localize_intentional_blanks/static/wagtail_localize_intentional_blanks/css/translation-editor.css +114 -0
- wagtail_localize_intentional_blanks/static/wagtail_localize_intentional_blanks/js/translation-editor.js +644 -0
- wagtail_localize_intentional_blanks/templates/wagtail_localize/admin/edit_translation.html +17 -0
- wagtail_localize_intentional_blanks/templatetags/__init__.py +1 -0
- wagtail_localize_intentional_blanks/templatetags/intentional_blanks.py +52 -0
- wagtail_localize_intentional_blanks/urls.py +27 -0
- wagtail_localize_intentional_blanks/utils.py +415 -0
- wagtail_localize_intentional_blanks/views.py +340 -0
- wagtail_localize_intentional_blanks/wagtail_hooks.py +8 -0
- wagtail_localize_intentional_blanks-0.1.0.dist-info/METADATA +244 -0
- wagtail_localize_intentional_blanks-0.1.0.dist-info/RECORD +18 -0
- wagtail_localize_intentional_blanks-0.1.0.dist-info/WHEEL +5 -0
- wagtail_localize_intentional_blanks-0.1.0.dist-info/licenses/LICENSE +23 -0
- wagtail_localize_intentional_blanks-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Template tags and filters for intentional blanks.
|
|
3
|
+
|
|
4
|
+
These template tags can be used to display information about
|
|
5
|
+
translation status and "do not translate" markers in templates.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from django import template
|
|
9
|
+
|
|
10
|
+
from ..utils import get_source_fallback_stats, is_do_not_translate
|
|
11
|
+
|
|
12
|
+
register = template.Library()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@register.filter
|
|
16
|
+
def is_marked_do_not_translate(string_translation):
|
|
17
|
+
"""
|
|
18
|
+
Template filter to check if a StringTranslation is marked as "do not translate".
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
{% load intentional_blanks %}
|
|
22
|
+
{% if translation|is_marked_do_not_translate %}
|
|
23
|
+
<span class="badge">Do not translate</span>
|
|
24
|
+
{% endif %}
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
string_translation: StringTranslation instance
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
bool: True if marked as "do not translate"
|
|
31
|
+
"""
|
|
32
|
+
return is_do_not_translate(string_translation)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@register.simple_tag
|
|
36
|
+
def translation_stats(translation):
|
|
37
|
+
"""
|
|
38
|
+
Template tag to get translation statistics.
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
{% load intentional_blanks %}
|
|
42
|
+
{% translation_stats translation as stats %}
|
|
43
|
+
<p>{{ stats.do_not_translate }} segments marked as do not translate</p>
|
|
44
|
+
<p>{{ stats.manually_translated }} segments manually translated</p>
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
translation: Translation instance
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
dict: Statistics about translation
|
|
51
|
+
"""
|
|
52
|
+
return get_source_fallback_stats(translation)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
URL patterns for the intentional blanks API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.urls import path
|
|
6
|
+
|
|
7
|
+
from . import views
|
|
8
|
+
|
|
9
|
+
app_name = "wagtail_localize_intentional_blanks"
|
|
10
|
+
|
|
11
|
+
urlpatterns = [
|
|
12
|
+
path(
|
|
13
|
+
"translations/<int:translation_id>/segment/<int:segment_id>/do-not-translate/",
|
|
14
|
+
views.mark_segment_do_not_translate_view,
|
|
15
|
+
name="mark_segment_do_not_translate",
|
|
16
|
+
),
|
|
17
|
+
path(
|
|
18
|
+
"translations/<int:translation_id>/segment/<int:segment_id>/status/",
|
|
19
|
+
views.get_segment_status,
|
|
20
|
+
name="get_segment_status",
|
|
21
|
+
),
|
|
22
|
+
path(
|
|
23
|
+
"translations/<int:translation_id>/status/",
|
|
24
|
+
views.get_translation_status,
|
|
25
|
+
name="get_translation_status",
|
|
26
|
+
),
|
|
27
|
+
]
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for marking segments as "do not translate".
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from django.db.models import Q
|
|
8
|
+
from wagtail_localize.models import StringSegment, StringTranslation
|
|
9
|
+
|
|
10
|
+
from .constants import get_setting
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_marker():
|
|
16
|
+
"""Get the configured marker value."""
|
|
17
|
+
return get_setting("MARKER")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_backup_separator():
|
|
21
|
+
"""
|
|
22
|
+
Get the configured backup separator value.
|
|
23
|
+
|
|
24
|
+
The backup separator is used when encoding backup values in the marker.
|
|
25
|
+
Format: MARKER + BACKUP_SEPARATOR + original_value
|
|
26
|
+
Example: "__DO_NOT_TRANSLATE__|backup|original_value"
|
|
27
|
+
"""
|
|
28
|
+
return get_setting("BACKUP_SEPARATOR")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_configuration():
|
|
32
|
+
"""
|
|
33
|
+
Validate that required configuration values are set.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If marker or backup_separator is None or empty
|
|
37
|
+
"""
|
|
38
|
+
marker = get_marker()
|
|
39
|
+
backup_separator = get_backup_separator()
|
|
40
|
+
|
|
41
|
+
if marker is None or marker == "":
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"WAGTAIL_LOCALIZE_INTENTIONAL_BLANKS_MARKER must be set to a non-empty string. Check your Django settings."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if backup_separator is None or backup_separator == "":
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"WAGTAIL_LOCALIZE_INTENTIONAL_BLANKS_BACKUP_SEPARATOR must be set to a non-empty string. Check your Django settings."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def mark_segment_do_not_translate(translation, segment, user=None):
|
|
53
|
+
"""
|
|
54
|
+
Mark a translation segment as "do not translate".
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
translation: Translation instance
|
|
58
|
+
segment: StringSegment instance
|
|
59
|
+
user: Optional user who made the change (for audit log)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The created/updated StringTranslation
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> from wagtail_localize.models import Translation, StringSegment
|
|
66
|
+
>>> translation = Translation.objects.get(id=123)
|
|
67
|
+
>>> segment = StringSegment.objects.get(id=456)
|
|
68
|
+
>>> mark_segment_do_not_translate(translation, segment)
|
|
69
|
+
"""
|
|
70
|
+
validate_configuration()
|
|
71
|
+
marker = get_marker()
|
|
72
|
+
|
|
73
|
+
logger.info(
|
|
74
|
+
f"Marking segment: string_id={segment.string.id}, locale={translation.target_locale}, context='{segment.context}', marker='{marker}'"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Check if there's an existing translation with real data (not the marker)
|
|
78
|
+
backup_separator = get_backup_separator()
|
|
79
|
+
try:
|
|
80
|
+
existing = StringTranslation.objects.get(
|
|
81
|
+
translation_of=segment.string,
|
|
82
|
+
locale=translation.target_locale,
|
|
83
|
+
context=segment.context,
|
|
84
|
+
)
|
|
85
|
+
# Encode backup in the marker itself
|
|
86
|
+
if existing.data != marker and not existing.data.startswith(
|
|
87
|
+
marker + backup_separator
|
|
88
|
+
):
|
|
89
|
+
backup_data = existing.data
|
|
90
|
+
logger.info(f"Backing up existing translation: '{backup_data}'")
|
|
91
|
+
# Encode backup in the data field: __DO_NOT_TRANSLATE__|backup|original_value
|
|
92
|
+
marker_with_backup = f"{marker}{backup_separator}{backup_data}"
|
|
93
|
+
else:
|
|
94
|
+
marker_with_backup = marker
|
|
95
|
+
except StringTranslation.DoesNotExist:
|
|
96
|
+
marker_with_backup = marker
|
|
97
|
+
|
|
98
|
+
string_translation, created = StringTranslation.objects.update_or_create(
|
|
99
|
+
translation_of=segment.string,
|
|
100
|
+
locale=translation.target_locale,
|
|
101
|
+
context=segment.context,
|
|
102
|
+
defaults={
|
|
103
|
+
"data": marker_with_backup,
|
|
104
|
+
"translation_type": StringTranslation.TRANSLATION_TYPE_MANUAL,
|
|
105
|
+
"last_translated_by": user,
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
logger.info(
|
|
110
|
+
f"StringTranslation {'created' if created else 'updated'}: id={string_translation.id}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return string_translation
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def unmark_segment_do_not_translate(translation, segment):
|
|
117
|
+
"""
|
|
118
|
+
Remove "do not translate" marking, allowing manual translation.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
translation: Translation instance
|
|
122
|
+
segment: StringSegment instance
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
int: Number of records deleted
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
>>> unmark_segment_do_not_translate(translation, segment)
|
|
129
|
+
"""
|
|
130
|
+
validate_configuration()
|
|
131
|
+
marker = get_marker()
|
|
132
|
+
backup_separator = get_backup_separator()
|
|
133
|
+
|
|
134
|
+
# Log what we're trying to delete
|
|
135
|
+
logger.info(
|
|
136
|
+
f"Attempting to unmark segment: string_id={segment.string.id}, "
|
|
137
|
+
f"locale={translation.target_locale}, context='{segment.context}', marker='{marker}'"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# First, let's see ALL StringTranslation records for this string in this locale
|
|
141
|
+
all_for_string = StringTranslation.objects.filter(
|
|
142
|
+
translation_of=segment.string, locale=translation.target_locale
|
|
143
|
+
)
|
|
144
|
+
logger.info(
|
|
145
|
+
f"Total StringTranslation records for this string in locale: {all_for_string.count()}"
|
|
146
|
+
)
|
|
147
|
+
for st in all_for_string:
|
|
148
|
+
logger.info(f" - id={st.id}, context='{st.context}', data='{st.data}'")
|
|
149
|
+
|
|
150
|
+
# Find the marked translation (could be just marker or marker with encoded backup)
|
|
151
|
+
try:
|
|
152
|
+
# Try to find with exact marker first
|
|
153
|
+
try:
|
|
154
|
+
marked_translation = StringTranslation.objects.get(
|
|
155
|
+
translation_of=segment.string,
|
|
156
|
+
locale=translation.target_locale,
|
|
157
|
+
context=segment.context,
|
|
158
|
+
data=marker,
|
|
159
|
+
)
|
|
160
|
+
backup_data = None
|
|
161
|
+
except StringTranslation.DoesNotExist:
|
|
162
|
+
# Try to find with encoded backup
|
|
163
|
+
marked_translation = StringTranslation.objects.get(
|
|
164
|
+
translation_of=segment.string,
|
|
165
|
+
locale=translation.target_locale,
|
|
166
|
+
context=segment.context,
|
|
167
|
+
data__startswith=marker + backup_separator,
|
|
168
|
+
)
|
|
169
|
+
# Extract backup from encoded data: __DO_NOT_TRANSLATE__|backup|original_value
|
|
170
|
+
backup_data = (
|
|
171
|
+
marked_translation.data.split(backup_separator, 1)[1]
|
|
172
|
+
if backup_separator in marked_translation.data
|
|
173
|
+
else None
|
|
174
|
+
)
|
|
175
|
+
logger.info(f"Found backup in data field: '{backup_data}'")
|
|
176
|
+
|
|
177
|
+
if backup_data:
|
|
178
|
+
# Restore the backup
|
|
179
|
+
logger.info(f"Restoring backup translation: '{backup_data}'")
|
|
180
|
+
marked_translation.data = backup_data
|
|
181
|
+
marked_translation.save()
|
|
182
|
+
return 1 # Updated
|
|
183
|
+
else:
|
|
184
|
+
# No backup, just delete
|
|
185
|
+
marked_translation.delete()
|
|
186
|
+
logger.info("Deleted marked translation (no backup)")
|
|
187
|
+
return 1 # Deleted
|
|
188
|
+
|
|
189
|
+
except StringTranslation.DoesNotExist:
|
|
190
|
+
logger.info("No matching StringTranslation found to delete")
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def is_do_not_translate(string_translation):
|
|
195
|
+
"""
|
|
196
|
+
Check if a StringTranslation is marked as "do not translate".
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
string_translation: StringTranslation instance
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
bool: True if marked as "do not translate"
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
>>> from wagtail_localize.models import StringTranslation
|
|
206
|
+
>>> st = StringTranslation.objects.get(id=789)
|
|
207
|
+
>>> if is_do_not_translate(st):
|
|
208
|
+
... print("Marked as do not translate")
|
|
209
|
+
"""
|
|
210
|
+
validate_configuration()
|
|
211
|
+
marker = get_marker()
|
|
212
|
+
backup_separator = get_backup_separator()
|
|
213
|
+
return string_translation.data == marker or string_translation.data.startswith(
|
|
214
|
+
marker + backup_separator
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_source_fallback_stats(translation):
|
|
219
|
+
"""
|
|
220
|
+
Get statistics on how many segments are marked as "do not translate".
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
translation: Translation instance
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
dict with counts:
|
|
227
|
+
- total: Total translated segments
|
|
228
|
+
- do_not_translate: Segments marked as "do not translate"
|
|
229
|
+
- manually_translated: Segments with manual translations
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
>>> stats = get_source_fallback_stats(translation)
|
|
233
|
+
>>> print(f"{stats['do_not_translate']} segments marked as do not translate")
|
|
234
|
+
"""
|
|
235
|
+
validate_configuration()
|
|
236
|
+
marker = get_marker()
|
|
237
|
+
backup_separator = get_backup_separator()
|
|
238
|
+
|
|
239
|
+
# Get all string IDs from segments belonging to this translation source
|
|
240
|
+
string_ids = StringSegment.objects.filter(source=translation.source).values_list(
|
|
241
|
+
"string_id", flat=True
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Query translations for these strings in the target locale
|
|
245
|
+
all_translations = StringTranslation.objects.filter(
|
|
246
|
+
locale=translation.target_locale, translation_of_id__in=string_ids
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Match both exact marker and encoded backup format
|
|
250
|
+
do_not_translate = all_translations.filter(
|
|
251
|
+
Q(data=marker) | Q(data__startswith=marker + backup_separator)
|
|
252
|
+
)
|
|
253
|
+
manually_translated = all_translations.exclude(
|
|
254
|
+
Q(data=marker) | Q(data__startswith=marker + backup_separator)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
"total": all_translations.count(),
|
|
259
|
+
"do_not_translate": do_not_translate.count(),
|
|
260
|
+
"manually_translated": manually_translated.count(),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def bulk_mark_segments(translation, segments, user=None):
|
|
265
|
+
"""
|
|
266
|
+
Mark multiple segments as "do not translate" in a single operation.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
translation: Translation instance
|
|
270
|
+
segments: Iterable of StringSegment instances
|
|
271
|
+
user: Optional user who made the change
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
int: Number of segments marked
|
|
275
|
+
|
|
276
|
+
Example:
|
|
277
|
+
>>> segments = StringSegment.objects.filter(source=source)[:10]
|
|
278
|
+
>>> count = bulk_mark_segments(translation, segments)
|
|
279
|
+
>>> print(f"Marked {count} segments")
|
|
280
|
+
"""
|
|
281
|
+
count = 0
|
|
282
|
+
|
|
283
|
+
for segment in segments:
|
|
284
|
+
mark_segment_do_not_translate(translation, segment, user=user)
|
|
285
|
+
count += 1
|
|
286
|
+
|
|
287
|
+
return count
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def get_segments_do_not_translate(translation):
|
|
291
|
+
"""
|
|
292
|
+
Get all segments that are marked as "do not translate" for a translation.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
translation: Translation instance
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
QuerySet of StringSegment instances
|
|
299
|
+
|
|
300
|
+
Example:
|
|
301
|
+
>>> segments = get_segments_do_not_translate(translation)
|
|
302
|
+
>>> for segment in segments:
|
|
303
|
+
... print(f"Segment {segment.id}: {segment.string.data}")
|
|
304
|
+
"""
|
|
305
|
+
validate_configuration()
|
|
306
|
+
marker = get_marker()
|
|
307
|
+
backup_separator = get_backup_separator()
|
|
308
|
+
|
|
309
|
+
# Get all string IDs from segments belonging to this translation source
|
|
310
|
+
string_ids = StringSegment.objects.filter(source=translation.source).values_list(
|
|
311
|
+
"string_id", flat=True
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Find translations marked as "do not translate" (match both exact marker and encoded backup format)
|
|
315
|
+
marked_string_translations = StringTranslation.objects.filter(
|
|
316
|
+
locale=translation.target_locale, translation_of_id__in=string_ids
|
|
317
|
+
).filter(Q(data=marker) | Q(data__startswith=marker + backup_separator))
|
|
318
|
+
|
|
319
|
+
# Get the string IDs of marked translations
|
|
320
|
+
marked_string_ids = marked_string_translations.values_list(
|
|
321
|
+
"translation_of_id", flat=True
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Return the segments that have these marked strings
|
|
325
|
+
return StringSegment.objects.filter(
|
|
326
|
+
source=translation.source, string_id__in=marked_string_ids
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def migrate_do_not_translate_markers(translation_source, target_locale):
|
|
331
|
+
"""
|
|
332
|
+
Migrate "Do Not Translate" markers when source Strings change.
|
|
333
|
+
|
|
334
|
+
When the source page content changes, wagtail-localize creates new String
|
|
335
|
+
objects. This function finds StringTranslation records with the marker that
|
|
336
|
+
point to old Strings and updates them to point to the current Strings based
|
|
337
|
+
on matching context paths.
|
|
338
|
+
|
|
339
|
+
This ensures that "Do Not Translate" markings persist across sync operations.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
translation_source: TranslationSource instance
|
|
343
|
+
target_locale: Target Locale instance
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
int: Number of StringTranslation records migrated
|
|
347
|
+
|
|
348
|
+
Example:
|
|
349
|
+
>>> from wagtail_localize.models import TranslationSource, Locale
|
|
350
|
+
>>> source = TranslationSource.objects.get(id=123)
|
|
351
|
+
>>> locale = Locale.objects.get(language_code='fr')
|
|
352
|
+
>>> count = migrate_do_not_translate_markers(source, locale)
|
|
353
|
+
>>> print(f"Migrated {count} markers")
|
|
354
|
+
"""
|
|
355
|
+
validate_configuration()
|
|
356
|
+
marker = get_marker()
|
|
357
|
+
backup_separator = get_backup_separator()
|
|
358
|
+
|
|
359
|
+
# Get all current StringSegments for this source
|
|
360
|
+
current_segments = StringSegment.objects.filter(
|
|
361
|
+
source=translation_source
|
|
362
|
+
).select_related("string", "context")
|
|
363
|
+
|
|
364
|
+
migrated_count = 0
|
|
365
|
+
|
|
366
|
+
# For each current segment, check if there's an old marker to migrate
|
|
367
|
+
for segment in current_segments:
|
|
368
|
+
# Find orphanzed markers - these are StringTranslations with the marker
|
|
369
|
+
# for this context+locale that DON'T point to the current String.
|
|
370
|
+
orphaned_markers = (
|
|
371
|
+
StringTranslation.objects.filter(
|
|
372
|
+
locale=target_locale,
|
|
373
|
+
context=segment.context,
|
|
374
|
+
)
|
|
375
|
+
.filter(Q(data=marker) | Q(data__startswith=marker + backup_separator))
|
|
376
|
+
.exclude(translation_of=segment.string)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# If we found orphaned markers, migrate them to the current String
|
|
380
|
+
for orphaned_marker in orphaned_markers:
|
|
381
|
+
old_string_id = orphaned_marker.translation_of_id
|
|
382
|
+
logger.info(
|
|
383
|
+
f"Migrating marker: context='{segment.context}', "
|
|
384
|
+
f"old_string_id={old_string_id} -> new_string_id={segment.string.id}, "
|
|
385
|
+
f"locale={target_locale}"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Check if there's already a StringTranslation for the new String
|
|
389
|
+
# (this can happen if wagtail-localize created one during sync)
|
|
390
|
+
existing_for_new_string = StringTranslation.objects.filter(
|
|
391
|
+
translation_of=segment.string,
|
|
392
|
+
locale=target_locale,
|
|
393
|
+
context=segment.context,
|
|
394
|
+
).exclude(id=orphaned_marker.id)
|
|
395
|
+
|
|
396
|
+
if existing_for_new_string.exists():
|
|
397
|
+
# Delete the existing one to avoid unique constraint violation
|
|
398
|
+
logger.info(
|
|
399
|
+
f"Deleting existing StringTranslation for new String "
|
|
400
|
+
f"to avoid conflict: {existing_for_new_string.first().id}"
|
|
401
|
+
)
|
|
402
|
+
existing_for_new_string.delete()
|
|
403
|
+
|
|
404
|
+
# Update the StringTranslation to point to the new String
|
|
405
|
+
orphaned_marker.translation_of = segment.string
|
|
406
|
+
orphaned_marker.save()
|
|
407
|
+
|
|
408
|
+
migrated_count += 1
|
|
409
|
+
|
|
410
|
+
if migrated_count > 0:
|
|
411
|
+
logger.info(
|
|
412
|
+
f"Migrated {migrated_count} 'Do Not Translate' markers for source {translation_source.id} to locale {target_locale}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
return migrated_count
|