Repository: saleor/saleor
Pull request: Drop translation proxy
Issue: https://github.com/saleor/saleor/issues/13143

I want to merge this change, because TranslationProxy is no longer useful as we don't rely on localisation middleware. Any references to the translated property should be fixed to return proper translation in the current context, ie. for checkout and order usages we should rely on language_code that can be set by mutations to provide the locale.

Essentially, this PR replaces `translated` property with `get_translation(language_code)` function and remove `translated` property from models, which do not utilise it at all.
<!-- Please mention all relevant issue numbers. -->

# Impact

* [ ] New migrations
* [ ] New/Updated API fields or mutations
* [ ] Deprecated API fields or mutations
* [ ] Removed API types, fields, or mutations
* [x] Documentation needs to be updated

# Pull Request Checklist

<!-- Please keep this section. It will make the maintainer's life easier. -->

* [ ] Privileged queries and mutations are either absent or guarded by proper permission checks
* [ ] Database queries are optimized and the number of queries is constant
* [ ] Database migrations are either absent or optimized for zero downtime
* [ ] The changes are covered by test cases


Problem statement:
Drop TranslationProxy
Drop the `TranslationProxy`, which is no longer useful as we don't rely on localization middleware. Any references to the `translated` property should be fixed to return proper translation in the current context. For checkout and order usages we should rely on `language_code` that can be set by mutations to provide the locale.

Hints from issue discussion:

Commit message: Update changelog.

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1f3fa4b7b1..d7df111ac0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,8 @@ All notable, unreleased changes to this project will be documented in this file.
 - Fix Error Cannot return null for non-nullable field Webhook.name. - #12989 by @cyborg7898
 - Remove unused `django-versatileimagefield` package - #13148 by @SzymJ
 - Remove unused `google-measurement-protocol` package - #13146 by @SzymJ
+- Drop TranslationProxy and replace `translated` model property with `get_translation` function where needed. - #13156 by @zedzior
+
 
 # 3.14.0
 
diff --git a/saleor/attribute/models/base.py b/saleor/attribute/models/base.py
index cce7f1db504..2a0838ddc65 100644
--- a/saleor/attribute/models/base.py
+++ b/saleor/attribute/models/base.py
@@ -8,7 +8,7 @@
 from ...core.models import ModelWithExternalReference, ModelWithMetadata, SortableModel
 from ...core.units import MeasurementUnits
 from ...core.utils.editorjs import clean_editor_js
-from ...core.utils.translations import Translation, TranslationProxy
+from ...core.utils.translations import Translation, get_translation
 from ...page.models import Page, PageType
 from ...permission.enums import PageTypePermissions, ProductTypePermissions
 from ...permission.utils import has_one_of_permissions
@@ -165,7 +165,6 @@ class Attribute(ModelWithMetadata, ModelWithExternalReference):
     available_in_grid = models.BooleanField(default=False, blank=True)
 
     objects = AttributeManager()
-    translated = TranslationProxy()
 
     class Meta(ModelWithMetadata.Meta):
         ordering = ("storefront_search_position", "slug")
@@ -252,8 +251,6 @@ class AttributeValue(SortableModel, ModelWithExternalReference):
         Page, related_name="references", on_delete=models.CASCADE, null=True, blank=True
     )
 
-    translated = TranslationProxy()
-
     class Meta:
         ordering = ("sort_order", "pk")
         unique_together = ("slug", "attribute")
@@ -276,6 +273,9 @@ def input_type(self):
     def get_ordering_queryset(self):
         return self.attribute.values.all()
 
+    def get_translation(self, language_code=None):
+        return get_translation(self, language_code)
+
 
 class AttributeValueTranslation(Translation):
     attribute_value = models.ForeignKey(
diff --git a/saleor/checkout/tests/test_checkout.py b/saleor/checkout/tests/test_checkout.py
index aeaed4050e2..9b7b2116361 100644
--- a/saleor/checkout/tests/test_checkout.py
+++ b/saleor/checkout/tests/test_checkout.py
@@ -1158,11 +1158,8 @@ def test_checkout_discount_amount_should_be_decimal():
 def test_recalculate_checkout_discount(
     checkout_with_voucher,
     voucher,
-    voucher_translation_fr,
-    settings,
     channel_USD,
 ):
-    settings.LANGUAGE_CODE = "fr"
     voucher.channel_listings.filter(channel=channel_USD).update(discount_value=10)
 
     manager = get_plugins_manager()
@@ -1170,9 +1167,6 @@ def test_recalculate_checkout_discount(
     checkout_info = fetch_checkout_info(checkout_with_voucher, lines, manager)
 
     recalculate_checkout_discount(manager, checkout_info, lines)
-    assert (
-        checkout_with_voucher.translated_discount_name == voucher_translation_fr.name
-    )  # noqa
     assert checkout_with_voucher.discount == Money("10.00", "USD")
 
 
@@ -1338,6 +1332,33 @@ def test_recalculate_checkout_discount_free_shipping_for_checkout_without_shippi
     assert checkout.discount == zero_money(checkout.channel.currency_code)
 
 
+def test_recalculate_checkout_discount_translate_discount_in_checkout_language(
+    checkout_with_voucher,
+    voucher,
+    voucher_translation_fr,
+    settings,
+    channel_USD,
+):
+    # given
+    language_code = "fr"
+    settings.LANGUAGE_CODE = "es"
+    checkout = checkout_with_voucher
+    checkout.language_code = language_code
+    checkout.save(update_fields=["language_code"])
+
+    voucher.channel_listings.filter(channel=channel_USD).update(discount_value=10)
+
+    manager = get_plugins_manager()
+    lines, _ = fetch_checkout_lines(checkout)
+    checkout_info = fetch_checkout_info(checkout, lines, manager)
+
+    # when
+    recalculate_checkout_discount(manager, checkout_info, lines)
+
+    # then
+    assert checkout.translated_discount_name == voucher_translation_fr.name
+
+
 def test_change_address_in_checkout(checkout, address):
     manager = get_plugins_manager()
     lines, _ = fetch_checkout_lines(checkout)
diff --git a/saleor/checkout/utils.py b/saleor/checkout/utils.py
index ce553261283..8491a566f53 100644
--- a/saleor/checkout/utils.py
+++ b/saleor/checkout/utils.py
@@ -620,9 +620,10 @@ def recalculate_checkout_discount(
                 else discount
             )
             checkout.discount_name = voucher.name
+            language_code = checkout.language_code
             checkout.translated_discount_name = (
-                voucher.translated.name
-                if voucher.translated.name != voucher.name
+                voucher.get_translation(language_code).name
+                if voucher.get_translation(language_code).name != voucher.name
                 else ""
             )
             checkout.save(
@@ -722,8 +723,11 @@ def add_voucher_to_checkout(
     )
     checkout.voucher_code = voucher.code
     checkout.discount_name = voucher.name
+    language_code = checkout.language_code
     checkout.translated_discount_name = (
-        voucher.translated.name if voucher.translated.name != voucher.name else ""
+        voucher.get_translation(language_code).name
+        if voucher.get_translation(language_code).name != voucher.name
+        else ""
     )
     checkout.discount = discount
     checkout.save(
diff --git a/saleor/core/utils/translations.py b/saleor/core/utils/translations.py
index 9c7531c62a9..0acb0f7a29d 100644
--- a/saleor/core/utils/translations.py
+++ b/saleor/core/utils/translations.py
@@ -1,7 +1,7 @@
 from typing import Any, Dict, Tuple, Union
 
+from django.conf import settings
 from django.db import models
-from django.utils.translation import get_language
 
 
 class TranslationWrapper:
@@ -27,12 +27,6 @@ def __str__(self):
         return str(instance)
 
 
-class TranslationProxy:
-    def __get__(self, instance, owner):
-        locale = get_language()
-        return TranslationWrapper(instance, locale)
-
-
 class Translation(models.Model):
     language_code = models.CharField(max_length=35)
 
@@ -51,3 +45,9 @@ def get_translated_keys(self) -> Dict[str, Any]:
 
     def get_translation_context(self) -> Dict[str, Any]:
         return {}
+
+
+def get_translation(instance, language_code) -> TranslationWrapper:
+    if not language_code:
+        language_code = settings.LANGUAGE_CODE
+    return TranslationWrapper(instance, language_code)
diff --git a/saleor/discount/models.py b/saleor/discount/models.py
index 11f5dfa0c5f..6cd20d36b4e 100644
--- a/saleor/discount/models.py
+++ b/saleor/discount/models.py
@@ -17,7 +17,7 @@
 
 from ..channel.models import Channel
 from ..core.models import ModelWithMetadata
-from ..core.utils.translations import Translation, TranslationProxy
+from ..core.utils.translations import Translation, get_translation
 from ..permission.enums import DiscountPermissions
 from . import DiscountType, DiscountValueType, VoucherType
 
@@ -95,7 +95,6 @@ class Voucher(ModelWithMetadata):
     categories = models.ManyToManyField("product.Category", blank=True)
 
     objects = VoucherManager()
-    translated = TranslationProxy()
 
     class Meta:
         ordering = ("code",)
@@ -171,6 +170,9 @@ def validate_only_for_staff(self, customer: Optional["User"]):
             msg = "This offer is valid only for staff customers."
             raise NotApplicable(msg)
 
+    def get_translation(self, language_code=None):
+        return get_translation(self, language_code)
+
 
 class VoucherChannelListing(models.Model):
     voucher = models.ForeignKey(
@@ -272,7 +274,6 @@ class Sale(ModelWithMetadata):
     notification_sent_datetime = models.DateTimeField(null=True, blank=True)
 
     objects = SaleManager()
-    translated = TranslationProxy()
 
     class Meta:
         ordering = ("name", "pk")
diff --git a/saleor/menu/models.py b/saleor/menu/models.py
index c85a77fb035..0f87c26f44f 100644
--- a/saleor/menu/models.py
+++ b/saleor/menu/models.py
@@ -3,7 +3,7 @@
 from mptt.models import MPTTModel
 
 from ..core.models import ModelWithMetadata, SortableModel
-from ..core.utils.translations import Translation, TranslationProxy
+from ..core.utils.translations import Translation
 from ..page.models import Page
 from ..permission.enums import MenuPermissions
 from ..product.models import Category, Collection
@@ -40,7 +40,6 @@ class MenuItem(ModelWithMetadata, MPTTModel, SortableModel):
 
     objects = models.Manager()
     tree = TreeManager()  # type: ignore[django-manager-missing]
-    translated = TranslationProxy()
 
     class Meta(ModelWithMetadata.Meta):
         ordering = ("sort_order", "pk")
diff --git a/saleor/order/tests/test_order.py b/saleor/order/tests/test_order.py
index c2c47239b46..daab2dff7b1 100644
--- a/saleor/order/tests/test_order.py
+++ b/saleor/order/tests/test_order.py
@@ -92,14 +92,11 @@ def test_recalculate_order_keeps_weight_unit(order_with_lines):
 def test_add_variant_to_order_adds_line_for_new_variant(
     order_with_lines,
     product,
-    product_translation_fr,
-    settings,
     anonymous_plugins,
 ):
     order = order_with_lines
     variant = product.variants.get()
     lines_before = order.lines.count()
-    settings.LANGUAGE_CODE = "fr"
     line_data = OrderLineData(variant_id=str(variant.id), variant=variant, quantity=1)
 
     add_variant_to_order(
@@ -116,7 +113,6 @@ def test_add_variant_to_order_adds_line_for_new_variant(
     assert line.product_variant_id == variant.get_global_id()
     assert line.quantity == 1
     assert line.unit_price == TaxedMoney(net=Money(10, "USD"), gross=Money(10, "USD"))
-    assert line.translated_product_name == str(variant.product.translated)
     assert line.variant_name == str(variant)
     assert line.product_name == str(variant.product)
     assert not line.unit_discount_amount
@@ -127,10 +123,8 @@ def test_add_variant_to_order_adds_line_for_new_variant(
 def test_add_variant_to_order_adds_line_for_new_variant_on_sale(
     order_with_lines,
     product,
-    product_translation_fr,
     sale,
     discount_info,
-    settings,
     anonymous_plugins,
 ):
     order = order_with_lines
@@ -138,7 +132,6 @@ def test_add_variant_to_order_adds_line_for_new_variant_on_sale(
     discount_info.variants_ids.add(variant.id)
     sale.variants.add(variant)
     lines_before = order.lines.count()
-    settings.LANGUAGE_CODE = "fr"
     line_data = OrderLineData(variant_id=str(variant.id), variant=variant, quantity=1)
 
     add_variant_to_order(
@@ -162,7 +155,6 @@ def test_add_variant_to_order_adds_line_for_new_variant_on_sale(
     assert line.unit_price == TaxedMoney(
         net=Money(unit_amount, "USD"), gross=Money(unit_amount, "USD")
     )
-    assert line.translated_product_name == str(variant.product.translated)
     assert line.variant_name == str(variant)
     assert line.product_name == str(variant.product)
 
@@ -174,8 +166,6 @@ def test_add_variant_to_order_adds_line_for_new_variant_on_sale(
 def test_add_variant_to_draft_order_adds_line_for_variant_with_price_0(
     order_with_lines,
     product,
-    product_translation_fr,
-    settings,
     anonymous_plugins,
 ):
     order = order_with_lines
@@ -185,7 +175,6 @@ def test_add_variant_to_draft_order_adds_line_for_variant_with_price_0(
     variant_channel_listing.save(update_fields=["price_amount", "currency"])
 
     lines_before = order.lines.count()
-    settings.LANGUAGE_CODE = "fr"
     line_data = OrderLineData(variant_id=str(variant.id), variant=variant, quantity=1)
 
     add_variant_to_order(
@@ -202,7 +191,6 @@ def test_add_variant_to_draft_order_adds_line_for_variant_with_price_0(
     assert line.product_variant_id == variant.get_global_id()
     assert line.quantity == 1
     assert line.unit_price == TaxedMoney(net=Money(0, "USD"), gross=Money(0, "USD"))
-    assert line.translated_product_name == str(variant.product.translated)
     assert line.product_name == variant.product.name
 
 
@@ -1344,10 +1332,8 @@ def test_order_update_charge_data_with_transaction_item_and_payment(
 def test_add_variant_to_order_adds_line_for_new_variant_on_sale_with_custom_price(
     order_with_lines,
     product,
-    product_translation_fr,
     sale,
     discount_info,
-    settings,
     anonymous_plugins,
 ):
     # given
@@ -1356,7 +1342,6 @@ def test_add_variant_to_order_adds_line_for_new_variant_on_sale_with_custom_pric
     discount_info.variants_ids.add(variant.id)
     sale.variants.add(variant)
     lines_before = order.lines.count()
-    settings.LANGUAGE_CODE = "fr"
     price_override = Decimal(15)
     line_data = OrderLineData(
         variant_id=str(variant.id),
@@ -1387,7 +1372,6 @@ def test_add_variant_to_order_adds_line_for_new_variant_on_sale_with_custom_pric
     assert line.unit_price == TaxedMoney(
         net=Money(unit_amount, "USD"), gross=Money(unit_amount, "USD")
     )
-    assert line.translated_product_name == str(variant.product.translated)
     assert line.variant_name == str(variant)
     assert line.product_name == str(variant.product)
 
@@ -1399,15 +1383,12 @@ def test_add_variant_to_order_adds_line_for_new_variant_on_sale_with_custom_pric
 def test_add_variant_to_order_adds_line_with_custom_price_for_new_variant(
     order_with_lines,
     product,
-    product_translation_fr,
-    settings,
     anonymous_plugins,
 ):
     # given
     order = order_with_lines
     variant = product.variants.get()
     lines_before = order.lines.count()
-    settings.LANGUAGE_CODE = "fr"
     price_override = Decimal(18)
     line_data = OrderLineData(
         variant_id=str(variant.id),
@@ -1436,7 +1417,6 @@ def test_add_variant_to_order_adds_line_with_custom_price_for_new_variant(
     assert line.unit_price == TaxedMoney(
         net=Money(price_override, "USD"), gross=Money(price_override, "USD")
     )
-    assert line.translated_product_name == str(variant.product.translated)
     assert line.variant_name == str(variant)
     assert line.product_name == str(variant.product)
     assert line.base_unit_price_amount == price_override
@@ -1444,3 +1424,39 @@ def test_add_variant_to_order_adds_line_with_custom_price_for_new_variant(
     assert not line.unit_discount_amount
     assert not line.unit_discount_value
     assert not line.unit_discount_reason
+
+
+def test_add_variant_to_order_adds_translations_in_order_language(
+    order_with_lines,
+    product,
+    product_translation_fr,
+    variant_translation_fr,
+    settings,
+    anonymous_plugins,
+):
+    # given
+    language_code = "fr"
+    settings.LANGUAGE_CODE = "es"
+
+    order = order_with_lines
+    order.language_code = language_code
+    order.save(update_fields=["language_code"])
+    variant = variant_translation_fr.product_variant
+    line_data = OrderLineData(variant_id=str(variant.id), variant=variant, quantity=1)
+
+    # when
+    add_variant_to_order(
+        order=order,
+        line_data=line_data,
+        user=None,
+        app=None,
+        manager=anonymous_plugins,
+    )
+
+    # then
+    line = order.lines.last()
+    assert line.quantity == 1
+    assert line.translated_product_name == str(
+        variant.product.get_translation(language_code)
+    )
+    assert line.translated_variant_name == str(variant.get_translation(language_code))
diff --git a/saleor/order/utils.py b/saleor/order/utils.py
index 36a5e1d0738..4c46a67a9fb 100644
--- a/saleor/order/utils.py
+++ b/saleor/order/utils.py
@@ -256,8 +256,9 @@ def create_order_line(
 
     product_name = str(product)
     variant_name = str(variant)
-    translated_product_name = str(product.translated)
-    translated_variant_name = str(variant.translated)
+    language_code = order.language_code
+    translated_product_name = str(product.get_translation(language_code))
+    translated_variant_name = str(variant.get_translation(language_code))
     if translated_product_name == product_name:
         translated_product_name = ""
     if translated_variant_name == variant_name:
diff --git a/saleor/page/models.py b/saleor/page/models.py
index 9a22946dbbc..7fb0ece705b 100644
--- a/saleor/page/models.py
+++ b/saleor/page/models.py
@@ -6,7 +6,6 @@
 from ..core.db.fields import SanitizedJSONField
 from ..core.models import ModelWithMetadata, PublishableModel, PublishedQuerySet
 from ..core.utils.editorjs import clean_editor_js
-from ..core.utils.translations import TranslationProxy
 from ..permission.enums import PagePermissions, PageTypePermissions
 from ..seo.models import SeoModel, SeoModelTranslation
 
@@ -34,8 +33,6 @@ class Page(ModelWithMetadata, SeoModel, PublishableModel):
     content = SanitizedJSONField(blank=True, null=True, sanitizer=clean_editor_js)
     created_at = models.DateTimeField(auto_now_add=True, db_index=True)
 
-    translated = TranslationProxy()
-
     objects = PageManager()  # type: ignore[assignment]
 
     class Meta(ModelWithMetadata.Meta):
diff --git a/saleor/product/migrations/0059_generate_variant_name_from_attrs.py b/saleor/product/migrations/0059_generate_variant_name_from_attrs.py
index becf46b6207..886a556edae 100644
--- a/saleor/product/migrations/0059_generate_variant_name_from_attrs.py
+++ b/saleor/product/migrations/0059_generate_variant_name_from_attrs.py
@@ -16,7 +16,7 @@ def get_attributes_display_map(obj, attributes):
     for attribute in attributes:
         attribute_values = obj.attributes.get(str(attribute.pk))  # type: List
         if attribute_values:
-            choices = {str(a.pk): a.translated for a in attribute.values.all()}
+            choices = {str(a.pk): a.get_translation() for a in attribute.values.all()}
             for value in attribute_values:
                 current_display_value = display_map[attribute.pk]
                 if not current_display_value:
diff --git a/saleor/product/models.py b/saleor/product/models.py
index ca5129e9f28..728b7f38716 100644
--- a/saleor/product/models.py
+++ b/saleor/product/models.py
@@ -49,7 +49,7 @@
 from ..core.units import WeightUnits
 from ..core.utils import build_absolute_uri
 from ..core.utils.editorjs import clean_editor_js
-from ..core.utils.translations import Translation, TranslationProxy
+from ..core.utils.translations import Translation, get_translation
 from ..core.weight import zero_weight
 from ..discount import DiscountInfo
 from ..discount.utils import calculate_discounted_price
@@ -94,7 +94,6 @@ class Category(ModelWithMetadata, MPTTModel, SeoModel):
 
     objects = models.Manager()
     tree = TreeManager()  # type: ignore[django-manager-missing]
-    translated = TranslationProxy()
 
     class Meta:
         indexes = [
@@ -442,7 +441,6 @@ class Product(SeoModel, ModelWithMetadata, ModelWithExternalReference):
     )
 
     objects = ProductManager()
-    translated = TranslationProxy()
 
     class Meta:
         app_label = "product"
@@ -494,6 +492,9 @@ def get_first_image(self):
     def sort_by_attribute_fields() -> list:
         return ["concatenated_values_order", "concatenated_values", "name"]
 
+    def get_translation(self, language_code=None):
+        return get_translation(self, language_code)
+
 
 class ProductTranslation(SeoModelTranslation):
     product = models.ForeignKey(
@@ -625,7 +626,6 @@ class ProductVariant(SortableModel, ModelWithMetadata, ModelWithExternalReferenc
     )
 
     objects = ProductVariantManager()
-    translated = TranslationProxy()
 
     class Meta(ModelWithMetadata.Meta):
         ordering = ("sort_order", "sku")
@@ -683,8 +683,8 @@ def is_digital(self) -> bool:
 
     def display_product(self, translated: bool = False) -> str:
         if translated:
-            product = self.product.translated
-            variant_display = str(self.translated)
+            product = self.product.get_translation()
+            variant_display = str(self.get_translation())
         else:
             variant_display = str(self)
             product = self.product
@@ -701,6 +701,9 @@ def is_preorder_active(self):
             self.preorder_end_date is None or timezone.now() <= self.preorder_end_date
         )
 
+    def get_translation(self, language_code=None):
+        return get_translation(self, language_code)
+
 
 class ProductVariantTranslation(Translation):
     product_variant = models.ForeignKey(
@@ -708,8 +711,6 @@ class ProductVariantTranslation(Translation):
     )
     name = models.CharField(max_length=255, blank=True)
 
-    translated = TranslationProxy()
-
     class Meta:
         unique_together = (("language_code", "product_variant"),)
 
@@ -946,8 +947,6 @@ class Collection(SeoModel, ModelWithMetadata):
 
     objects = CollectionManager()
 
-    translated = TranslationProxy()
-
     class Meta(ModelWithMetadata.Meta):
         ordering = ("slug",)
         indexes = [
diff --git a/saleor/product/tests/test_translation.py b/saleor/product/tests/test_translation.py
index 20d47e3e762..a91381f26a3 100644
--- a/saleor/product/tests/test_translation.py
+++ b/saleor/product/tests/test_translation.py
@@ -1,13 +1,8 @@
 import pytest
 
-from ...attribute.models import AttributeTranslation, AttributeValueTranslation
+from ...attribute.models import AttributeValueTranslation
 from ...tests.utils import dummy_editorjs
-from ..models import (
-    CategoryTranslation,
-    CollectionTranslation,
-    ProductTranslation,
-    ProductVariantTranslation,
-)
+from ..models import ProductTranslation, ProductVariantTranslation
 
 
 @pytest.fixture
@@ -29,95 +24,65 @@ def attribute_value_translation_fr(translated_attribute):
 
 
 def test_translation(product, settings, product_translation_fr):
-    assert product.translated.name == "Test product"
-    assert not product.translated.description
+    assert product.get_translation().name == "Test product"
+    assert not product.get_translation().description
 
     settings.LANGUAGE_CODE = "fr"
-    assert product.translated.name == "French name"
-    assert product.translated.description == dummy_editorjs("French description.")
+    assert product.get_translation().name == "French name"
+    assert product.get_translation().description == dummy_editorjs(
+        "French description."
+    )
 
 
 def test_translation_str_returns_str_of_instance(
     product, product_translation_fr, settings
 ):
-    assert str(product.translated) == str(product)
+    assert str(product.get_translation()) == str(product)
     settings.LANGUAGE_CODE = "fr"
-    assert str(product.translated.translation) == str(product_translation_fr)
+    assert str(product.get_translation().translation) == str(product_translation_fr)
 
 
 def test_wrapper_gets_proper_wrapper(
     product, product_translation_fr, settings, product_translation_pl
 ):
-    assert product.translated.translation is None
+    assert product.get_translation().translation is None
 
     settings.LANGUAGE_CODE = "fr"
-    assert product.translated.translation == product_translation_fr
+    assert product.get_translation().translation == product_translation_fr
 
     settings.LANGUAGE_CODE = "pl"
-    assert product.translated.translation == product_translation_pl
+    assert product.get_translation().translation == product_translation_pl
 
 
 def test_getattr(product, settings, product_translation_fr, product_type):
     settings.LANGUAGE_CODE = "fr"
-    assert product.translated.product_type == product_type
+    assert product.get_translation().product_type == product_type
 
 
 def test_translation_not_override_id(settings, product, product_translation_fr):
     settings.LANGUAGE_CODE = "fr"
-    translated_product = product.translated
+    translated_product = product.get_translation()
     assert translated_product.id == product.id
     assert not translated_product.id == product_translation_fr
 
 
-def test_collection_translation(settings, collection):
-    settings.LANGUAGE_CODE = "fr"
-    french_name = "French name"
-    CollectionTranslation.objects.create(
-        language_code="fr", name=french_name, collection=collection
-    )
-    assert collection.translated.name == french_name
-
-
-def test_category_translation(settings, category):
-    settings.LANGUAGE_CODE = "fr"
-    french_name = "French name"
-    french_description = dummy_editorjs("French description.")
-    CategoryTranslation.objects.create(
-        language_code="fr",
-        name=french_name,
-        description=french_description,
-        category=category,
-    )
-    assert category.translated.name == french_name
-    assert category.translated.description == french_description
-
-
 def test_product_variant_translation(settings, variant):
     settings.LANGUAGE_CODE = "fr"
     french_name = "French name"
     ProductVariantTranslation.objects.create(
         language_code="fr", name=french_name, product_variant=variant
     )
-    assert variant.translated.name == french_name
-
-
-def test_attribute_translation(settings, color_attribute):
-    AttributeTranslation.objects.create(
-        language_code="fr", attribute=color_attribute, name="French name"
-    )
-    assert not color_attribute.translated.name == "French name"
-    settings.LANGUAGE_CODE = "fr"
-    assert color_attribute.translated.name == "French name"
+    assert variant.get_translation().name == french_name
 
 
 def test_attribute_value_translation(settings, product, attribute_value_translation_fr):
     attribute = product.product_type.product_attributes.first().values.first()
-    assert not attribute.translated.name == "French name"
+    assert not attribute.get_translation().name == "French name"
     settings.LANGUAGE_CODE = "fr"
-    assert attribute.translated.name == "French name"
+    assert attribute.get_translation().name == "French name"
 
 
 def test_voucher_translation(settings, voucher, voucher_translation_fr):
-    assert not voucher.translated.name == "French name"
+    assert not voucher.get_translation().name == "French name"
     settings.LANGUAGE_CODE = "fr"
-    assert voucher.translated.name == "French name"
+    assert voucher.get_translation().name == "French name"
diff --git a/saleor/shipping/models.py b/saleor/shipping/models.py
index 8c55ac6213b..26003b81658 100644
--- a/saleor/shipping/models.py
+++ b/saleor/shipping/models.py
@@ -15,7 +15,7 @@
 from ..core.models import ModelWithMetadata
 from ..core.units import WeightUnits
 from ..core.utils.editorjs import clean_editor_js
-from ..core.utils.translations import Translation, TranslationProxy
+from ..core.utils.translations import Translation
 from ..core.weight import convert_weight, get_default_weight_unit, zero_weight
 from ..permission.enums import ShippingPermissions
 from ..tax.models import TaxClass
@@ -219,7 +219,6 @@ class ShippingMethod(ModelWithMetadata):
     )
 
     objects = ShippingMethodManager()
-    translated = TranslationProxy()
 
     class Meta(ModelWithMetadata.Meta):
         ordering = ("pk",)
diff --git a/saleor/shipping/tests/test_translation.py b/saleor/shipping/tests/test_translation.py
deleted file mode 100644
index 4014038ca11..00000000000
--- a/saleor/shipping/tests/test_translation.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import pytest
-
-from ..models import ShippingMethodTranslation
-
-
-@pytest.fixture
-def shipping_method_translation_fr(shipping_method):
-    return ShippingMethodTranslation.objects.create(
-        language_code="fr", shipping_method=shipping_method, name="French name"
-    )
-
-
-def shipping_method_translation(
-    settings, shipping_method, shipping_method_translation_fr
-):
-    assert not shipping_method.translated.name == "French name"
-    settings.LANGUAGE_CODE = "fr"
-    assert shipping_method.translated.name == "French name"
diff --git a/saleor/site/models.py b/saleor/site/models.py
index 0807ed35088..1613b65580f 100644
--- a/saleor/site/models.py
+++ b/saleor/site/models.py
@@ -10,7 +10,7 @@
 
 from ..core import TimePeriodType
 from ..core.units import WeightUnits
-from ..core.utils.translations import Translation, TranslationProxy
+from ..core.utils.translations import Translation
 from ..permission.enums import SitePermissions
 from . import GiftCardSettingsExpiryType
 from .error_codes import SiteErrorCode
@@ -98,8 +98,6 @@ class SiteSettings(models.Model):
     include_taxes_in_prices = models.BooleanField(default=True)
     display_gross_prices = models.BooleanField(default=True)
 
-    translated = TranslationProxy()
-
     class Meta:
         permissions = (
             (SitePermissions.MANAGE_SETTINGS.codename, "Manage settings."),


Code review comments:
diff_hunk: @@ -620,9 +620,10 @@ def recalculate_checkout_discount(
                 else discount
             )
             checkout.discount_name = voucher.name
+            language_code = checkout.language_code
             checkout.translated_discount_name = (
-                voucher.translated.name
-                if voucher.translated.name != voucher.name
+                voucher.get_translation(language_code).name
+                if voucher.get_translation(language_code).name != voucher.name
path: saleor/checkout/utils.py
text: @user1:
I would even remove the `get_translation` method from models and call directly the `translations.get_translation` function in places like this. Also, I would call it once as it might perform database queries (I guess):
```
voucher_translation = get_translation(voucher, language_code)
checkout.translated_discount_name = voucher_translation.name if voucher_translation != name else ""
```