Skip to content

Commit e297bc2

Browse files
committed
Translation cache – v1.0
1 parent 1704a5c commit e297bc2

File tree

4 files changed

+77
-2
lines changed

4 files changed

+77
-2
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 4.2.19 on 2025-07-29 15:43
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("lang", "0006_alter_string_unique_together"),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name="TranslationCache",
15+
fields=[
16+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
17+
("text", models.TextField()),
18+
("source_language", models.CharField(max_length=16)),
19+
("dest_language", models.CharField(max_length=16)),
20+
("translated_text", models.TextField()),
21+
("created_at", models.DateTimeField(auto_now_add=True)),
22+
],
23+
options={
24+
"indexes": [
25+
models.Index(fields=["text", "source_language", "dest_language"], name="lang_transl_text_4a497b_idx")
26+
],
27+
"unique_together": {("text", "source_language", "dest_language")},
28+
},
29+
),
30+
]

lang/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,20 @@ def get_user_permissions_per_language(cls, user):
3333
return {
3434
lang_code: user.has_perm(cls._get_permission_per_language_codename(lang_code)) for lang_code, _ in settings.LANGUAGES
3535
}
36+
37+
38+
class TranslationCache(models.Model):
39+
text = models.TextField()
40+
source_language = models.CharField(max_length=16)
41+
dest_language = models.CharField(max_length=16)
42+
translated_text = models.TextField()
43+
created_at = models.DateTimeField(auto_now_add=True)
44+
45+
class Meta:
46+
unique_together = ("text", "source_language", "dest_language")
47+
indexes = [
48+
models.Index(fields=["text", "source_language", "dest_language"]),
49+
]
50+
51+
def __str__(self):
52+
return f"{self.source_language}>{self.dest_language}: {self.text[:30]}..."

lang/tests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
from unittest import mock
33

4+
import pytest
45
from django.conf import settings
56
from django.contrib.auth.models import Permission
67
from django.core import management
@@ -227,6 +228,7 @@ def test_lang_api_permissions(self):
227228

228229
class TranslatorMockTest(unittest.TestCase):
229230

231+
@pytest.mark.django_db
230232
@mock.patch("lang.translation.requests")
231233
def test_ifrc_translator(self, requests_mock):
232234
# Simple mock test where we define what the expected response is from provider

lang/translation.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from django.conf import settings
88
from django.utils.module_loading import import_string
99

10+
from .models import TranslationCache
11+
1012
logger = logging.getLogger(__name__)
1113

1214

@@ -133,7 +135,21 @@ def translate_text(self, text, dest_language, source_language=None):
133135
# NOTE: Sending 'text' throws 500 from IFRC translation endpoint
134136
# So only sending if html
135137
payload["textType"] = "html"
136-
logger.info(f"IFRC translation API call content: {text[:30]}... to {dest_language} from {source_language}")
138+
139+
# Try cache at first (for shorter texts)
140+
use_cache = len(text) < 200
141+
142+
if use_cache:
143+
cache = TranslationCache.objects.filter(
144+
text=text,
145+
source_language=source_language or "", # source_language can be "detected"
146+
dest_language=dest_language,
147+
).first()
148+
if cache:
149+
logger.info(f"translation cache hit: {text[:30]}... {source_language}>{dest_language}")
150+
return cache.translated_text
151+
152+
logger.info(f"IFRC translation API call: {text[:30]}... {source_language}>{dest_language}")
137153
response = requests.post(
138154
self.url,
139155
headers=self.headers,
@@ -142,7 +158,17 @@ def translate_text(self, text, dest_language, source_language=None):
142158

143159
# Not using == 200 – it would break tests with MagicMock name=requests.post() results
144160
if response.status_code != 500:
145-
return response.json()[0]["translations"][0]["text"] + textTail
161+
translated = response.json()[0]["translations"][0]["text"]
162+
163+
# Cache the translation if original text was short enough
164+
if use_cache:
165+
TranslationCache.objects.create(
166+
text=text,
167+
source_language=source_language or "", # source_language can be "detected"
168+
dest_language=dest_language,
169+
translated_text=translated,
170+
)
171+
return translated + textTail
146172

147173

148174
def get_translator_class():

0 commit comments

Comments
 (0)