feat: lazy global translated strings
This commit is contained in:
parent
1f83fd8847
commit
1f6201b4af
6 changed files with 95 additions and 36 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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")},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."),
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue