|
|
@@ -7,8 +7,8 @@ |
|
|
|
Translation tools for frappe |
|
|
|
""" |
|
|
|
|
|
|
|
import ast |
|
|
|
import functools |
|
|
|
import io |
|
|
|
import itertools |
|
|
|
import json |
|
|
|
import operator |
|
|
@@ -682,7 +682,7 @@ def get_all_messages_from_js_files(app_name=None): |
|
|
|
return messages |
|
|
|
|
|
|
|
|
|
|
|
def get_messages_from_file(path: str) -> list[tuple[str, str, str, str]]: |
|
|
|
def get_messages_from_file(path: str) -> list[tuple[str, str, str | None, int]]: |
|
|
|
"""Returns a list of transatable strings from a code file |
|
|
|
|
|
|
|
:param path: path of the code file |
|
|
@@ -704,19 +704,71 @@ def get_messages_from_file(path: str) -> list[tuple[str, str, str, str]]: |
|
|
|
print(f"Could not scan file for translation: {path}") |
|
|
|
return [] |
|
|
|
|
|
|
|
if path.lower().endswith(".py"): |
|
|
|
messages = extract_messages_from_python_code(file_contents) |
|
|
|
else: |
|
|
|
messages = extract_messages_from_code(file_contents) |
|
|
|
return [ |
|
|
|
(os.path.relpath(path, bench_path), message, context, line) |
|
|
|
for (line, message, context) in extract_messages_from_code(file_contents) |
|
|
|
for (line, message, context) in messages |
|
|
|
] |
|
|
|
else: |
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
def extract_messages_from_python_code(code: str) -> list[tuple[int, str, str | None]]: |
|
|
|
"""Extracts translatable strings from python code using AST""" |
|
|
|
|
|
|
|
tree = ast.parse(code) |
|
|
|
|
|
|
|
TRANSLATE_FUNCTION = "_" |
|
|
|
messages = [] |
|
|
|
|
|
|
|
for node in ast.walk(tree): |
|
|
|
if not isinstance(node, ast.Call): |
|
|
|
continue |
|
|
|
|
|
|
|
# frappe._(...) |
|
|
|
direct_call = isinstance(node.func, ast.Attribute) and node.func.attr == TRANSLATE_FUNCTION |
|
|
|
# from frappe import _ |
|
|
|
# _(...) |
|
|
|
imported_call = isinstance(node.func, ast.Name) and node.func.id == TRANSLATE_FUNCTION |
|
|
|
|
|
|
|
if not (direct_call or imported_call): |
|
|
|
continue |
|
|
|
|
|
|
|
message = _extract_message_from_translation_node(node) |
|
|
|
if message: |
|
|
|
messages.append(message) |
|
|
|
|
|
|
|
return messages |
|
|
|
|
|
|
|
|
|
|
|
def _extract_message_from_translation_node(node: ast.Call) -> tuple[int, str, str | None]: |
|
|
|
|
|
|
|
# extract source text |
|
|
|
source_text = None |
|
|
|
if node.args and isinstance(node.args[0], ast.Constant): |
|
|
|
source_text = node.args[0].value |
|
|
|
if not isinstance(source_text, str): |
|
|
|
# you can have non-str default args which don't make sense for translations |
|
|
|
return |
|
|
|
|
|
|
|
# Extract context, a kwarg called "context" should exist. |
|
|
|
context = None |
|
|
|
for kw in node.keywords: |
|
|
|
if kw.arg != "context": |
|
|
|
continue |
|
|
|
if isinstance(kw.value, ast.Constant) and isinstance(kw.value.value, str): |
|
|
|
context = kw.value.value |
|
|
|
|
|
|
|
return (node.lineno, source_text, context) |
|
|
|
|
|
|
|
|
|
|
|
def extract_messages_from_code(code): |
|
|
|
""" |
|
|
|
Extracts translatable strings from a code file |
|
|
|
:param code: code from which translatable files are to be extracted |
|
|
|
:param is_py: include messages in triple quotes e.g. `_('''message''')` |
|
|
|
""" |
|
|
|
from jinja2 import TemplateError |
|
|
|
|
|
|
|