Skip to content

Commit

Permalink
chore: refactor translation memory updates
Browse files Browse the repository at this point in the history
Avoid repeated fetching of objects from the database by doing most
operations in the main process. These are cheap, the only expensive part
is updating memory entries which is now a separate task.
  • Loading branch information
nijel committed Dec 11, 2024
1 parent 7914b4d commit d5cbda3
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 53 deletions.
137 changes: 91 additions & 46 deletions weblate/memory/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations

from typing import TYPE_CHECKING, TypedDict, cast
from typing import TYPE_CHECKING

from django.db import transaction

Expand Down Expand Up @@ -33,66 +33,81 @@ def import_memory(project_id: int, component_id: int | None = None) -> None:
component.log_info("updating translation memory")
with transaction.atomic():
units = Unit.objects.filter(
translation__component=component, state__gte=STATE_TRANSLATED
translation__component=component,
state__gte=STATE_TRANSLATED,
translation__component__is_glossary=False,
).exclude(target="")
if not component.intermediate:
units = units.exclude(
translation__language_id=component.source_language_id
)
for unit in units.prefetch_related("translation", "translation__language"):
update_memory(None, unit, component, project)
handle_unit_translation_change(unit, None, component, project)


@app.task(trail=False)
def handle_unit_translation_change(unit_id: int, user_id: int | None = None) -> None:
from weblate.auth.models import User
from weblate.trans.models import Unit

user = None if user_id is None else User.objects.get(pk=user_id)
try:
unit = Unit.objects.prefetch().get(pk=unit_id)
except Unit.DoesNotExist:
# Unit was removed meanwhile
return
update_memory(user, unit)


class MemoryParams(TypedDict):
source_language: Language
target_language: Language
source: str
target: str
origin: str


def update_memory(
user: User | None,
def handle_unit_translation_change(
unit: Unit,
user: User | None = None,
component: Component | None = None,
project: Project | None = None,
) -> None:
component = component or unit.translation.component
project = project or component.project
params: MemoryParams = {
"source_language": get_machinery_language(component.source_language),
"target_language": get_machinery_language(unit.translation.language),
"source": unit.source,
"target": unit.target,
"origin": component.full_slug,
}

if not is_valid_memory_entry(**params):
if component is None:
component = unit.translation.component
if project is None:
project = component.project

# Do not keep per-user memory for bots
if user and user.is_bot:
user = None

source_language: Language = get_machinery_language(component.source_language)
target_language: Language = get_machinery_language(unit.translation.language)
source = unit.source
target = unit.target
origin = component.full_slug

if not is_valid_memory_entry(source=source, target=target):
return

update_memory.delay_on_commit(
source_language_id=source_language.id,
target_language_id=target_language.id,
source=source,
target=target,
origin=origin,
add_shared=project.contribute_shared_tm,
user_id=user.id if user is not None else None,
project_id=project.id,
)


@app.task(trail=False)
def update_memory(
*,
source_language_id: int,
target_language_id: int,
source: str,
target: str,
origin: str,
add_shared: bool,
user_id: int | None,
project_id: int,
) -> None:
add_project = True
add_shared = project.contribute_shared_tm
add_user = user is not None and not user.is_bot
add_user = user_id is not None

# Check matching entries in memory
for matching in Memory.objects.filter(from_file=False, **params):
for matching in Memory.objects.filter(
from_file=False,
source=source,
target=target,
origin=origin,
source_language_id=source_language_id,
target_language_id=target_language_id,
):
if (
matching.user_id is None
and matching.project_id == project.id
and matching.project_id == project_id
and not matching.shared
):
add_project = False
Expand All @@ -105,7 +120,7 @@ def update_memory(
add_shared = False
elif (
add_user
and matching.user_id == cast("User", user).id
and matching.user_id == user_id
and matching.project_id is None
and not matching.shared
):
Expand All @@ -115,15 +130,45 @@ def update_memory(

if add_project:
to_create.append(
Memory(user=None, project=project, from_file=False, shared=False, **params)
Memory(
user=None,
project_id=project_id,
from_file=False,
shared=False,
source=source,
target=target,
origin=origin,
source_language_id=source_language_id,
target_language_id=target_language_id,
)
)
if add_shared:
to_create.append(
Memory(user=None, project=None, from_file=False, shared=True, **params)
Memory(
user=None,
project=None,
from_file=False,
shared=True,
source=source,
target=target,
origin=origin,
source_language_id=source_language_id,
target_language_id=target_language_id,
)
)
if add_user:
to_create.append(
Memory(user=user, project=None, from_file=False, shared=False, **params)
Memory(
user_id=user_id,
project=None,
from_file=False,
shared=False,
source=source,
target=target,
origin=origin,
source_language_id=source_language_id,
target_language_id=target_language_id,
)
)
if to_create:
Memory.objects.bulk_create(to_create)
8 changes: 4 additions & 4 deletions weblate/memory/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,16 @@ def test_import_project(self) -> None:

def test_import_unit(self) -> None:
unit = self.get_unit()
handle_unit_translation_change(unit.id, self.user.id)
handle_unit_translation_change(unit, self.user)
self.assertEqual(Memory.objects.count(), 0)
handle_unit_translation_change(unit.id, self.user.id)
handle_unit_translation_change(unit, self.user)
self.assertEqual(Memory.objects.count(), 0)
unit.translate(self.user, "Nazdar", STATE_TRANSLATED)
self.assertEqual(Memory.objects.count(), 3)
Memory.objects.all().delete()
handle_unit_translation_change(unit.id, self.user.id)
handle_unit_translation_change(unit, self.user)
self.assertEqual(Memory.objects.count(), 3)
handle_unit_translation_change(unit.id, self.user.id)
handle_unit_translation_change(unit, self.user)
self.assertEqual(Memory.objects.count(), 3)


Expand Down
6 changes: 3 additions & 3 deletions weblate/trans/models/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1604,7 +1604,7 @@ def translate(
self.save(run_checks=False, same_content=True, update_fields=["state"])

if user and self.target != self.old_unit["target"]:
self.update_translation_memory(user.id)
self.update_translation_memory(user)

if change_action == Change.ACTION_AUTO:
self.labels.add(component.project.automatically_translated_label)
Expand Down Expand Up @@ -1886,7 +1886,7 @@ def update_explanation(
def glossary_sort_key(self):
return (self.translation.component.priority, self.source.lower())

def update_translation_memory(self, user_id: int | None = None) -> None:
def update_translation_memory(self, user: User | None = None) -> None:
translation = self.translation
component = translation.component
if (
Expand All @@ -1895,4 +1895,4 @@ def update_translation_memory(self, user_id: int | None = None) -> None:
and not component.is_glossary
and is_valid_memory_entry(source=self.source, target=self.target)
):
handle_unit_translation_change.delay_on_commit(self.id, user_id)
handle_unit_translation_change(self, user)

0 comments on commit d5cbda3

Please sign in to comment.