8000 refactor: accurate translation caching (backport #18595) by mergify[bot] · Pull Request #18598 · frappe/frappe · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

refactor: accurate translation caching (backport #18595) #18598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions frappe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _(msg, lang=None, context=None) -> str:
_('Change')
_('Change', context='Coins')
"""
from frappe.translate import get_full_dict
from frappe.translate import get_all_translations
from frappe.utils import is_html, strip_html_tags

< 8000 span class='blob-code-inner blob-code-marker ' data-code-marker=" "> if not hasattr(local, "lang"):
Expand All @@ -107,14 +107,15 @@ def _(msg, lang=None, context=None) -> str:
msg = as_unicode(msg).strip()

translated_string = ""

all_translations = get_all_translations(lang)
if context:
string_key = "{msg}:{context}".format(msg=msg, context=context)
translated_string = get_full_dict(lang).get(string_key)
string_key = f"{msg}:{context}"
translated_string = all_translations.get(string_key)

if not translated_string:
translated_string = get_full_dict(lang).get(msg)
translated_string = all_translations.get(msg)

# return lang_full_dict according to lang passed parameter
return translated_string or non_translated_string


Expand Down Expand Up @@ -221,7 +222,6 @@ def init(site, sites_path=None, new_site=False):

local.conf = _dict(get_site_config())
local.lang = local.conf.lang or "en"
local.lang_full_dict = None

local.module_app = None
local.app_modules = None
Expand Down Expand Up @@ -2022,7 +2022,7 @@ def logger(
)


def log_error(message=None, title=_("Error")):
def log_error(message=None, title="Error"):
"""Log error to Error Log"""

# AI ALERT:
Expand Down
2 changes: 2 additions & 0 deletions frappe/commands/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
)
def build(app=None, hard_link=False, make_copy=False, restore=False, verbose=False, force=False):
"Minify + concatenate JS and CSS files, build translations"
import frappe.build

frappe.init("")
# don't minify in developer_mode for faster builds
no_compress = frappe.local.conf.developer_mode or False
Expand Down
15 changes: 8 additions & 7 deletions frappe/core/doctype/translation/test_translation.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import unittest

import frappe
from frappe import _
from frappe.tests.utils import FrappeTestCase
from frappe.translate import clear_cache


class TestTranslation(unittest.TestCase):
class TestTranslation(FrappeTestCase):
def setUp(self):
frappe.db.sql("delete from tabTranslation")

def tearDown(self):
frappe.local.lang = "en"
frappe.local.lang_full_dict = None
clear_cache()

def test_doctype(self):
translation_data = get_translation_data()
for key, val in translation_data.items():
frappe.local.lang = key
frappe.local.lang_full_dict = None
translation = create_translation(key, val)
self.assertEqual(_(val[0]), val[1])

frappe.delete_doc("Translation", translation.name)
frappe.local.lang_full_dict = None

self.assertEqual(_(val[0]), val[0])

def test_parent_language(self):
Expand Down Expand Up @@ -58,6 +55,10 @@ def test_parent_language(self):
frappe.local.lang_full_dict = None
self.assertTrue(_(data[1][0]), data[1][1])

< 8000 /td> def test_multi_language_translations(self):
source = "User"
self.assertNotEqual(_(source, lang="de"), _(source, lang="es"))

def test_html_content_data_translation(self):
source = """
<span style="color: rgb(51, 51, 51); font-family: &quot;Amazon Ember&quot;, Arial, sans-serif; font-size:
Expand Down
5 changes: 3 additions & 2 deletions frappe/core/doctype/translation/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import frappe
from frappe.model.document import Document
from frappe.translate import get_translator_url
from frappe.translate import MERGED_TRANSLATION_KEY, USER_TRANSLATION_KEY, get_translator_url
from frappe.utils import is_html, strip_html_tags


Expand Down Expand Up @@ -92,4 +92,5 @@ def create_translations(translation_map, language):


def clear_user_translation_cache(lang):
frappe.cache().hdel("lang_user_translations", lang)< E7F5 /span>
frappe.cache().hdel(USER_TRANSLATION_KEY, lang)
frappe.cache().hdel(MERGED_TRANSLATION_KEY, lang)
1 change: 0 additions & 1 deletion frappe/tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ def test_link_field_order(self):
def test_link_search_in_foreign_language(self):
try:
frappe.local.lang = "fr"
frappe.local.lang_full_dict = None # discard translation cache
search_widget(doctype="DocType", txt="pay", page_length=20)
output = frappe.response["values"]

Expand Down
101 changes: 55 additions & 46 deletions frappe/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ def guess_language(lang_list=None):
)


# Cache keys
MERGED_TRANSLATION_KEY = "merged_translations"
APP_TRANSLATION_KEY = "translations_from_apps"
USER_TRANSLATION_KEY = "lang_user_translations"


def get_language(lang_list: List = None) -> str:
"""Set `frappe.local.lang` from HTTP headers at beginning of request

Expand Down Expand Up @@ -239,9 +245,9 @@ def make_dict_from_messages(messages, full_dict=None, load_user_translation=True
out = {}
if full_dict == None:
if load_user_translation:
full_dict = get_full_dict(frappe.local.lang)
full_dict = get_all_translations(frappe.local.lang)
else:
full_dict = load_lang(frappe.local.lang)
full_dict = get_translations_from_apps(frappe.local.lang)

for m in messages:
if m[1] in full_dict:
Expand All @@ -264,54 +270,50 @@ def get_lang_js(fortype: str, name: str) -> str:
return f"\n\n$.extend(frappe._messages, {json.dumps(get_dict(fortype, name))})"


def get_full_dict(lang):
"""Load and return the entire translations dictionary for a language from :meth:`frape.cache`
def get_all_translations(lang: str):
"""Load and return the entire translations dictionary for a language from apps + user translations.

:param lang: Language Code, e.g. `hi`
"""
if not lang:
return {}

# found in local, return!
if getattr(frappe.local, "lang_full_dict", None) is not None:
return frappe.local.lang_full_dict

frappe.local.lang_full_dict = load_lang(lang)
def _merge_translations():
all_translations = get_translations_from_apps(lang).copy()
try:
# get user specific translation data
user_translations = get_user_translations(lang)
all_translations.update(user_translations)
except Exception:
pass

try:
# get user specific translation data
user_translations = get_user_translations(lang)
frappe.local.lang_full_dict.update(user_translations)
except Exception:
pass
return all_translations

return frappe.local.lang_full_dict
return frappe.cache().hget(MERGED_TRANSLATION_KEY, lang, generator=_merge_translations)


def load_lang(lang, apps=None):
def get_translations_from_apps(lang, apps=None):
"""Combine all translations from `.csv` files in all `apps`.
For derivative languages (es-GT), take translations from the
base language (es) and then update translations from the child (es-GT)"""

if lang == "en":
return {}

out = frappe.cache().hget("lang_full_dict", lang, shared=True)
if not out:
out = {}
def _get_from_disk():
translations = {}
for app in apps or frappe.get_all_apps(True):
path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv")
out.update(get_translation_dict_from_file(path, lang, app) or {})

translations.update(get_translation_dict_from_file(path, lang, app) or {})
if "-" in lang:
parent = lang.split("-")[0]
parent_out = load_lang(parent)
parent_out.update(out)
out = parent_out
parent_translations = get_translations_from_apps(parent)
parent_translations.update(translations)
return parent_translations

frappe.cache().hset("lang_full_dict", lang, out, shared=True)
return translations

return out or {}
return frappe.cache().hget(APP_TRANSLATION_KEY, lang, shared=True, generator=_get_from_disk)


def get_translation_dict_from_file(path, lang, app, throw=False):
Expand Down Expand Up @@ -340,23 +342,22 @@ def get_translation_dict_from_file(path, lang, app, throw=False):
def get_user_translations(lang):
if not frappe.db:
frappe.connect()
out = frappe.cache().hget("lang_user_translations", lang)
if out is None:
out = {}
user_translations = frappe.get_all(

def _read_from_db():
user_translations = {}
translations = frappe.get_all(
"Translation", fields=["source_text", "translated_text", "context"], filters={"language": lang}
)

for translation in user_translations:
key = translation.source_text
value = translation.translated_text
if translation.context:
key += ":" + translation.context
out[key] = value

frappe.cache().hset("lang_user_translations", lang, out)
for t in translations:
key = t.source_text
value = t.translated_text
if t.context:
key += ":" + t.context
user_translations[key] = value
return user_translations

return out
return frappe.cache().hget(USER_TRANSLATION_KEY, lang, generator=_read_from_db)


def clear_cache():
Expand All @@ -366,9 +367,10 @@ def clear_cache():

# clear translations saved in boot cache
cache.delete_key("bootinfo")
cache.delete_key("lang_full_dict", shared=True)
cache.delete_key("translation_assets", shared=True)
cache.delete_key("lang_user_translations")
cache.delete_key(APP_TRANSLATION_KEY, shared=True)
cache.delete_key(USER_TRANSLATION_KEY)
cache.delete_key(MERGED_TRANSLATION_KEY)


def get_messages_for_app(app, deduplicate=True):
Expand Down Expand Up @@ -842,7 +844,7 @@ def escape_newlines(s):
# replace \n with ||| so that internal linebreaks don't get split
f.write((escape_newlines(m[1]) + os.linesep).encode("utf-8"))
else:
full_dict = get_full_dict(lang)
full_dict = get_all_translations(lang)

for m in messages:
if not full_dict.get(m[1]):
Expand All @@ -865,7 +867,7 @@ def update_translations(lang, untranslated_file, translated_file):
:param untranslated_file: File path with the messages in English.
:param translated_file: File path with messages in language to be updated."""
clear_cache()
full_dict = get_full_dict(lang)
full_dict = get_all_translations(lang)

def restore_newlines(s):
return (
Expand Down Expand Up @@ -895,7 +897,7 @@ def restore_newlines(s):
def import_translations(lang, path):
"""Import translations from file in standard format"""
clear_cache()
full_dict = get_full_dict(lang)
full_dict = get_all_translations(lang)
full_dict.update(get_translation_dict_from_file(path, lang, "import"))

for app in frappe.get_all_apps(True):
Expand Down Expand Up @@ -925,7 +927,9 @@ def write_translations_file(app, lang, full_dict=None, app_messages=None):

tpath = frappe.get_pymodule_path(app, "translations")
frappe.create_folder(tpath)
write_csv_file(os.path.join(tpath, lang + ".csv"), app_messages, full_dict or get_full_dict(lang))
write_csv_file(
os.path.join(tpath, lang + ".csv"), app_messages, full_dict or get_all_translations(lang)
)


def send_translations(translation_dict):
Expand Down Expand Up @@ -1087,3 +1091,8 @@ def get_translated_doctypes():
"Property Setter", {"property": "translated_doctype", "value": "1"}, pluck="doc_type"
)
return unique(dts + custom_dts)


# Backward compatibility
get_full_dict = get_all_translations
load_lang = get_translations_from_apps
0