Odoo18-Base/odoo/addons/test_lint/tests/test_i18n.py
2025-01-06 10:57:38 +07:00

129 lines
5.2 KiB
Python

import logging
import re
from . import lint_case
from odoo import tools
_logger = logging.getLogger(__name__)
class TestI18n(lint_case.LintCase):
PROPS_RE = re.compile(
r"""
(
<[A-Z] # Match the opening tag of a component node. We assume that tags starting with an uppercase letter refer to a component
(
[^>]+ # Match anything that is not a closing tag `>`. In other words, the rest of the component name and any prop that doesn't match the heuristics that follow
)
)
\s
(?!t-) # exclude directives (attributes starting with t-)
(
[a-zA-Z-]+ # Match prop name
=
"' # Make sure that the value is a static string literal. Only static string literals are eligible for translation. We determine that the value is a string literal if the value begins and ends with a '
[A-Z](\'|[^'"])*? # Assumption: Text starting with an uppercase letter is probably supposed to be translated.
[a-z] # Arbitrary constraint to avoid matching certain technical constants (e.g. ROW, COL)
(\'|[^'"])*? # Match the content of the string
'" # Value ends with the closing of a string literal
)
""",
re.VERBOSE | re.DOTALL,
)
def test_directives_regex(self):
"""
Checks that the regex:
- Catches components that are spread across multiple lines.
- Does not catch directives.
- Does not catch props that use `.translate`.
- Does not catch strings that do not start with a capital letter.
"""
test_cases = [
# Multi-line test case
(
"""
<Component
t-esc="some_variable"
customProp="'Custom String'"
/>""",
[
("customProp=\"'Custom String'\""),
],
),
# Exclude directives starting with t-
(
"""
<Component t-title="'Some String'" t-esc="some_variable"/>
""",
[],
),
# Doesn't catch .translate props
(
"""
<Component title.translate="'Some String'" t-esc="some_variable"/>
""",
[],
),
# Include valid cases
(
"""
<Component title="'Another String'" t-esc="another_variable"/>
<Component description="'Description here'" />
<Component title="'String with an escaped single quote ' inside'"/>
""",
[
("title=\"'Another String'\""),
("description=\"'Description here'\""),
("title=\"'String with an escaped single quote ' inside'\""),
],
),
# Exclude attributes starting with t- in between valid attributes
(
"""
<Component title="'Valid Title'" t-esc="some_variable" t-title="'Should not be caught'" customProp="'Valid Prop'"/>
""",
[
("customProp=\"'Valid Prop'\""),
],
),
# Ensure it catches strings starting with capital letter and exclude others
(
"""
<Component name="'singleword'" title="'SingleWord'" prop="'another String'"/>
""",
[
("title=\"'SingleWord'\""),
],
),
]
error_count = 0
for i, (file_content, expected_matches) in enumerate(test_cases):
matches = [(m.group(3)) for m in self.PROPS_RE.finditer(file_content)]
if matches != expected_matches:
_logger.error("Test case %s failed: expected %s, got %s", i + 1, expected_matches, matches)
error_count += 1
self.assertEqual(error_count, 0)
def test_user_content_as_prop_is_translatable(self):
"""
Checks if there are any props that does not use `.translate` and reports it.
"""
error_count = 0
for file_path in self.iter_module_files("**/static/**/*.xml"):
with tools.file_open(file_path, "r") as f:
file_content = f.read()
for m in self.PROPS_RE.finditer(file_content):
lineno = file_content[: m.start()].count("\n") + 1
_logger.error(
"""The prop “%s” in file “%s” in the component node starting at line %s contains what looks like human-readable text. If the content of this prop is intended for display to the end user, add the .translate suffix to make the prop translatable.
If this is a false positive, please contact the i18n team.""",
m.group(3),
file_path,
lineno,
)
error_count += 1
self.assertEqual(error_count, 0)