feat: lazy global translated strings

This commit is contained in:
Ankush Menat 2024-01-10 21:07:25 +05:30
parent 1f83fd8847
commit 1f6201b4af
6 changed files with 95 additions and 36 deletions

View file

@ -118,6 +118,12 @@ def _(msg: str, lang: str | None = None, context: str | None = None) -> str:
return translated_string or non_translated_string
def _lt(msg: str, lang: str | None = None, context: str | None = None):
from frappe.translate import LazyTranslate
return LazyTranslate(msg, lang, context)
def as_unicode(text, encoding: str = "utf-8") -> str:
"""Convert to unicode if required."""
if isinstance(text, str):

View file

@ -3,7 +3,7 @@
# model __init__.py
import frappe
from frappe import _
from frappe import _, _lt
data_fieldtypes = (
"Currency",
@ -134,22 +134,22 @@ log_types = (
)
std_fields = [
{"fieldname": "name", "fieldtype": "Link", "label": _("ID")},
{"fieldname": "owner", "fieldtype": "Link", "label": _("Created By"), "options": "User"},
{"fieldname": "idx", "fieldtype": "Int", "label": _("Index")},
{"fieldname": "creation", "fieldtype": "Datetime", "label": _("Created On")},
{"fieldname": "modified", "fieldtype": "Datetime", "label": _("Last Updated On")},
{"fieldname": "name", "fieldtype": "Link", "label": _lt("ID")},
{"fieldname": "owner", "fieldtype": "Link", "label": _lt("Created By"), "options": "User"},
{"fieldname": "idx", "fieldtype": "Int", "label": _lt("Index")},
{"fieldname": "creation", "fieldtype": "Datetime", "label": _lt("Created On")},
{"fieldname": "modified", "fieldtype": "Datetime", "label": _lt("Last Updated On")},
{
"fieldname": "modified_by",
"fieldtype": "Link",
"label": _("Last Updated By"),
"label": _lt("Last Updated By"),
"options": "User",
},
{"fieldname": "_user_tags", "fieldtype": "Data", "label": _("Tags")},
{"fieldname": "_liked_by", "fieldtype": "Data", "label": _("Liked By")},
{"fieldname": "_comments", "fieldtype": "Text", "label": _("Comments")},
{"fieldname": "_assign", "fieldtype": "Text", "label": _("Assigned To")},
{"fieldname": "docstatus", "fieldtype": "Int", "label": _("Document Status")},
{"fieldname": "_user_tags", "fieldtype": "Data", "label": _lt("Tags")},
{"fieldname": "_liked_by", "fieldtype": "Data", "label": _lt("Liked By")},
{"fieldname": "_comments", "fieldtype": "Text", "label": _lt("Comments")},
{"fieldname": "_assign", "fieldtype": "Text", "label": _lt("Assigned To")},
{"fieldname": "docstatus", "fieldtype": "Int", "label": _lt("Document Status")},
]

View file

@ -22,7 +22,7 @@ from datetime import datetime
import click
import frappe
from frappe import _
from frappe import _, _lt
from frappe.model import (
child_table_fields,
data_fieldtypes,
@ -42,17 +42,17 @@ from frappe.modules import load_doctype_module
from frappe.utils import cast, cint, cstr
DEFAULT_FIELD_LABELS = {
"name": lambda: _("ID"),
"creation": lambda: _("Created On"),
"docstatus": lambda: _("Document Status"),
"idx": lambda: _("Index"),
"modified": lambda: _("Last Updated On"),
"modified_by": lambda: _("Last Updated By"),
"owner": lambda: _("Created By"),
"_user_tags": lambda: _("Tags"),
"_liked_by": lambda: _("Liked By"),
"_comments": lambda: _("Comments"),
"_assign": lambda: _("Assigned To"),
"name": _lt("ID"),
"creation": _lt("Created On"),
"docstatus": _lt("Document Status"),
"idx": _lt("Index"),
"modified": _lt("Last Updated On"),
"modified_by": _lt("Last Updated By"),
"owner": _lt("Created By"),
"_user_tags": _lt("Tags"),
"_liked_by": _lt("Liked By"),
"_comments": _lt("Comments"),
"_assign": _lt("Assigned To"),
}
@ -249,7 +249,7 @@ class Meta(Document):
return df.get("label")
if fieldname in DEFAULT_FIELD_LABELS:
return DEFAULT_FIELD_LABELS[fieldname]()
return str(DEFAULT_FIELD_LABELS[fieldname])
return "No Label"

View file

@ -7,7 +7,7 @@ from unittest.mock import patch
import frappe
import frappe.translate
from frappe import _
from frappe import _, _lt
from frappe.gettext.extractors.javascript import extract_javascript
from frappe.tests.utils import FrappeTestCase
from frappe.translate import (
@ -32,6 +32,9 @@ first_lang, second_lang, third_lang, fourth_lang, fifth_lang = choices(
)
_lazy_translations = _lt("Communication")
class TestTranslate(FrappeTestCase):
guest_sessions_required = [
"test_guest_request_language_resolution_with_cookie",
@ -46,6 +49,7 @@ class TestTranslate(FrappeTestCase):
frappe.form_dict.pop("_lang", None)
if self._testMethodName in self.guest_sessions_required:
frappe.set_user("Administrator")
frappe.local.lang = "en"
def test_clear_cache(self):
_("Trigger caching")
@ -79,7 +83,6 @@ class TestTranslate(FrappeTestCase):
self.assertEqual(ext_line, exp_line)
def test_read_language_variant(self):
frappe.local.lang = "en"
self.assertEqual(_("Mobile No"), "Mobile No")
try:
frappe.local.lang = "pt-BR"
@ -91,12 +94,24 @@ class TestTranslate(FrappeTestCase):
self.assertEqual(_("Mobile No"), "Mobile No")
def test_translation_with_context(self):
try:
frappe.local.lang = "fr"
self.assertEqual(_("Change"), "Changement")
self.assertEqual(_("Change", context="Coins"), "la monnaie")
finally:
frappe.local.lang = "en"
frappe.local.lang = "fr"
self.assertEqual(_("Change"), "Changement")
self.assertEqual(_("Change", context="Coins"), "la monnaie")
def test_lazy_translations(self):
frappe.local.lang = "de"
eager_translation = _("Communication")
self.assertEqual(str(_lazy_translations), eager_translation)
self.assertRaises(NotImplementedError, lambda: _lazy_translations == "blah")
# auto casts when added or radded
self.assertEqual(_lazy_translations + "A", eager_translation + "A")
x = _lazy_translations
x += "A"
self.assertEqual(x, eager_translation + "A")
# f string usually auto-casts
self.assertEqual(f"{_lazy_translations}", eager_translation)
def test_request_language_resolution_with_form_dict(self):
"""Test for frappe.translate.get_language

View file

@ -1105,6 +1105,44 @@ def print_language(language: str):
frappe.local.jenv = _jenv
@functools.total_ordering
class LazyTranslate:
__slots__ = ("msg", "lang", "context")
def __init__(self, msg: str, lang: str | None = None, context: str | None = None) -> None:
self.msg = msg
self.lang = lang
self.context = context
@property
def value(self) -> str:
return frappe._(str(self.msg), self.lang, self.context)
def __str__(self):
return self.value
def __add__(self, other):
if isinstance(other, (str, LazyTranslate)):
return self.value + str(other)
raise NotImplementedError
def __radd__(self, other):
if isinstance(other, (str, LazyTranslate)):
return str(other) + self.value
return NotImplementedError
def __repr__(self) -> str:
return f"'{self.value}'"
# NOTE: it's required to override these methods and raise error as default behaviour will
# return `False` in all cases.
def __eq__(self, other):
raise NotImplementedError
def __lt__(self, other):
raise NotImplementedError
# Backward compatibility
get_full_dict = get_all_translations
load_lang = get_translations_from_apps

View file

@ -7,7 +7,7 @@ from zxcvbn import zxcvbn
from zxcvbn.scoring import ALL_UPPER, START_UPPER
import frappe
from frappe import _
from frappe import _, _lt
if TYPE_CHECKING:
from collections.abc import Iterable
@ -41,8 +41,8 @@ def test_password_strength(password: str, user_inputs: "Iterable[object]" = None
default_feedback: "PasswordStrengthFeedback" = {
"warning": "",
"suggestions": [
_("Use a few words, avoid common phrases."),
_("No need for symbols, digits, or uppercase letters."),
_lt("Use a few words, avoid common phrases."),
_lt("No need for symbols, digits, or uppercase letters."),
],
}