Merge pull request #24251 from ankush/translation_fixes

fix: misc translation fixes
This commit is contained in:
Ankush Menat 2024-01-10 22:05:09 +05:30 committed by GitHub
commit 5566d20705
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 39 deletions

View file

@ -118,6 +118,23 @@ 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):
"""Lazily translate a string.
This function returns a "lazy string" which when casted to string via some operation applies
translation first before casting.
This is only useful for translating strings in global scope or anything that potentially runs
before `frappe.init()`
Note: Result is not guaranteed to equivalent to pure strings for all operations.
"""
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

@ -7,7 +7,7 @@ from datetime import datetime
from pathlib import Path
from babel.messages.catalog import Catalog
from babel.messages.extract import extract_from_dir
from babel.messages.extract import DEFAULT_KEYWORDS, extract_from_dir
from babel.messages.mofile import read_mo, write_mo
from babel.messages.pofile import read_po, write_po
@ -128,6 +128,9 @@ def generate_pot(target_app: str | None = None):
apps = [target_app] if target_app else frappe.get_all_apps(True)
default_method_map = get_method_map("frappe")
keywords = DEFAULT_KEYWORDS.copy()
keywords["_lt"] = None
for app in apps:
app_path = frappe.get_pymodule_path(app)
catalog = get_catalog(app)
@ -138,7 +141,7 @@ def generate_pot(target_app: str | None = None):
method_map.extend(default_method_map)
for filename, lineno, message, comments, context in extract_from_dir(
app_path, method_map, directory_filter=directory_filter
app_path, method_map, directory_filter=directory_filter, keywords=keywords
):
if not message:
continue
@ -284,6 +287,8 @@ def get_translations_from_mo(lang, app):
locale_dir = get_locale_dir()
mo_file = gettext.find(app, locale_dir, (lang,))
if not mo_file:
return translations
with open(mo_file, "rb") as f:
catalog = read_mo(f)
for m in catalog:

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
@ -185,6 +200,7 @@ class TestTranslate(FrappeTestCase):
)
_(not_a_string)
_(not_a_string, context="wat")
_lt("Communication")
"""
)
expected_output = [
@ -194,6 +210,7 @@ class TestTranslate(FrappeTestCase):
(5, "name with", "name context"),
(6, "broken on", "new line"),
(10, "broken on separate line", None),
(15, "Communication", None),
]
output = extract_messages_from_python_code(code)

View file

@ -632,7 +632,7 @@ def extract_messages_from_python_code(code: str) -> list[tuple[int, str, str | N
for message in extract_python(
io.BytesIO(code.encode()),
keywords=["_"],
keywords=["_", "_lt"],
comment_tags=(),
options={},
):
@ -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."),
],
}