Skip to content
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
6 changes: 3 additions & 3 deletions builder/ai_page_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def emit(suffix, **kwargs):
for chunk in call_llm(model, messages, params, stream=True, api_key=api_key):
if delta := chunk.choices[0].delta.content:
if not content:
emit("progress", message="Building...")
emit("progress", message=_("Building..."))
last_stage = "Building..."
content += delta
if cache_key:
Expand All @@ -382,7 +382,7 @@ def emit(suffix, **kwargs):
if cache_key:
frappe.cache().delete_value(cache_key)
frappe.log_error(f"Parse error: {e}\nContent: {content}", f"{event_prefix} parse")
emit("error", message="Failed to parse AI response. The model returned invalid YAML.")
emit("error", message=_("Failed to parse AI response. The model returned invalid YAML."))
return

except Exception as e:
Expand Down Expand Up @@ -561,7 +561,7 @@ def get_progress_stage(content: str) -> str | None:
if name_match:
block_name = name_match.group(1).strip()
if block_name.lower() not in {"body", "root", "container"}:
return f"Building {block_name}"
return _("Building {0}").format(block_name)
return None


Expand Down
13 changes: 8 additions & 5 deletions builder/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import frappe
import requests
from frappe import _
from frappe.apps import get_apps as get_permitted_apps
from frappe.core.doctype.file.file import get_local_image
from frappe.core.doctype.file.utils import delete_file
Expand Down Expand Up @@ -36,7 +37,7 @@ def is_site_read_only() -> bool:
@frappe.whitelist()
def get_page_preview_html(page: str, **kwargs) -> Response:
if not frappe.has_permission("Builder Page", "read", page):
frappe.throw("No permission to preview this page")
frappe.throw(_("No permission to preview this page"))

# to load preview without publishing
frappe.form_dict.update(kwargs)
Expand Down Expand Up @@ -179,18 +180,20 @@ def assert_not_private_url(url: str) -> None:
"""Raise PermissionError if the URL resolves to a private/internal IP (SSRF guard)."""
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
frappe.throw("Only HTTP/HTTPS URLs are allowed for external images.", frappe.PermissionError)
frappe.throw(_("Only HTTP/HTTPS URLs are allowed for external images."), frappe.PermissionError)
hostname = parsed.hostname
if not hostname:
frappe.throw("Invalid URL: missing hostname.", frappe.ValidationError)
frappe.throw(_("Invalid URL: missing hostname."), frappe.ValidationError)
try:
addr_infos = socket.getaddrinfo(hostname, None)
except socket.gaierror:
frappe.throw(f"Could not resolve hostname: {hostname}", frappe.ValidationError)
frappe.throw(_("Could not resolve hostname: {0}").format(hostname), frappe.ValidationError)
for addr_info in addr_infos:
ip = ipaddress.ip_address(addr_info[4][0])
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast:
frappe.throw("Requests to private or internal addresses are not allowed.", frappe.PermissionError)
frappe.throw(
_("Requests to private or internal addresses are not allowed."), frappe.PermissionError
)


def check_app_permission():
Expand Down
2 changes: 1 addition & 1 deletion builder/builder/doctype/builder_page/builder_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def validate_access(self):
if self.docname:
self.doc = frappe.get_cached_doc(self.doctype, self.docname)
if self.doc.authenticated_access and frappe.session.user == "Guest":
raise frappe.PermissionError("Please log in to view this page.")
raise frappe.PermissionError(frappe._("Please log in to view this page."))

def set_canonical_url(self):
if not self.doc:
Expand Down
12 changes: 12 additions & 0 deletions builder/builder/tests/test_i18n.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from pathlib import Path
from unittest.mock import patch

import frappe
Expand Down Expand Up @@ -52,6 +53,17 @@ def is_frontend_src(dirpath) -> bool:
messages.setdefault(message, set()).add(filename.replace("\\", "/"))
return messages

def test_catalogs_carry_no_in_context_placeholders(self):
"""Crowdin's in-context pseudo language exports `crwdns...` ids instead of translations.

They score as fully translated, so nothing upstream rejects them, and compiling one
shows those ids across the whole editor.
"""
locale_dir = Path(frappe.get_app_path("builder")) / "locale"
polluted = [po.name for po in locale_dir.glob("*.po") if "crwdns" in po.read_text(encoding="utf-8")]

self.assertEqual(polluted, [], f"Crowdin in-context placeholders found in: {polluted}")

def test_boot_carries_only_builder_translations(self):
with patch("builder.www._builder.get_translations_from_apps", return_value={}) as from_apps:
get_boot()
Expand Down
Loading
Loading