diff --git a/frappe/boot.py b/frappe/boot.py index c29a2118e5..5ea9235b18 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -342,7 +342,10 @@ def get_user_pages_or_reports(parent, cache=False): def load_translations(bootinfo): + from frappe.translate import get_translation_version + bootinfo["lang"] = frappe.lang + bootinfo["translations_version"] = get_translation_version() def get_user_info(): diff --git a/frappe/core/doctype/translation/translation.py b/frappe/core/doctype/translation/translation.py index b4a55e794e..51fabeaf71 100644 --- a/frappe/core/doctype/translation/translation.py +++ b/frappe/core/doctype/translation/translation.py @@ -5,7 +5,7 @@ import json import frappe from frappe.model.document import Document -from frappe.translate import MERGED_TRANSLATION_KEY, USER_TRANSLATION_KEY +from frappe.translate import MERGED_TRANSLATION_KEY, USER_TRANSLATION_KEY, bump_translation_version from frappe.utils import is_html, strip_html_tags @@ -46,3 +46,4 @@ class Translation(Document): def clear_user_translation_cache(lang): frappe.cache.hdel(USER_TRANSLATION_KEY, lang) frappe.cache.hdel(MERGED_TRANSLATION_KEY, lang) + bump_translation_version() diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 56c114277a..187e3fc956 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -100,7 +100,7 @@ frappe.sys_defaults = frappe.boot.sysdefaults; frappe._messages = {}; frappe._translations_loaded = fetch( - `/api/method/frappe.translate.get_boot_translations?v=${window._version_number}&lang=${frappe.boot.lang}`, + `/api/method/frappe.translate.get_boot_translations?v=${frappe.boot.translations_version}&lang=${frappe.boot.lang}`, {credentials: "same-origin"} ).then(r => r.json()).then(data => { frappe._messages = data.message || {}; diff --git a/frappe/translate.py b/frappe/translate.py index 5504b54990..12bd60f8c8 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -20,6 +20,7 @@ from csv import reader, writer import frappe from frappe.query_builder import DocType, Field from frappe.utils import cstr, get_bench_path, is_html, strip, strip_html_tags, unique +from frappe.utils.caching import http_cache REPORT_TRANSLATE_PATTERN = re.compile('"([^:,^"]*):') CSV_STRIP_WHITESPACE_PATTERN = re.compile(r"{\s?([0-9]+)\s?}") @@ -28,6 +29,7 @@ CSV_STRIP_WHITESPACE_PATTERN = re.compile(r"{\s?([0-9]+)\s?}") # Cache keys MERGED_TRANSLATION_KEY = "merged_translations" USER_TRANSLATION_KEY = "lang_user_translations" +TRANSLATION_VERSION_KEY = "translation_version" def get_language(lang_list: list | None = None) -> str: @@ -133,9 +135,9 @@ def get_messages_for_boot(): @frappe.whitelist(allow_guest=True, methods=["GET"]) +@http_cache(max_age=31536000) def get_boot_translations(lang: str | None = None) -> dict[str, str]: """Return all translations for the current user's language.""" - frappe.local.response_headers["Cache-Control"] = "private, max-age=31536000" return get_all_translations(lang or frappe.local.lang) @@ -248,6 +250,24 @@ def clear_cache(): frappe.cache.delete_value( keys=["bootinfo", USER_TRANSLATION_KEY, MERGED_TRANSLATION_KEY], ) + bump_translation_version() + + +def get_translation_version() -> str: + """Return the current translation version from cache.""" + version = frappe.cache.get_value(TRANSLATION_VERSION_KEY) + if version is None: + version = 1 + frappe.cache.set_value(TRANSLATION_VERSION_KEY, version) + return str(version) + + +def bump_translation_version(): + """Increment the translation version so browser caches are invalidated.""" + try: + frappe.cache.incrby(TRANSLATION_VERSION_KEY, 1) + except Exception: + frappe.cache.set_value(TRANSLATION_VERSION_KEY, 1) def get_messages_for_app(app, deduplicate=True): diff --git a/frappe/www/desk.html b/frappe/www/desk.html index b1b40c7258..b1aa7d7749 100644 --- a/frappe/www/desk.html +++ b/frappe/www/desk.html @@ -56,7 +56,7 @@ frappe.csrf_token = "{{ csrf_token }}"; frappe._translations_loaded = fetch( - `/api/method/frappe.translate.get_boot_translations?v=${window._version_number}&lang=${frappe.boot.lang}`, + `/api/method/frappe.translate.get_boot_translations?v=${frappe.boot.translations_version}&lang=${frappe.boot.lang}`, { credentials: "same-origin", headers: {