localizationkit 2.0.0__tar.gz → 3.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {localizationkit-2.0.0 → localizationkit-3.0.1}/PKG-INFO +7 -5
- {localizationkit-2.0.0 → localizationkit-3.0.1}/README.md +1 -1
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/__init__.py +7 -8
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/configuration.py +7 -7
- localizationkit-3.0.1/localizationkit/exceptions.py +9 -0
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/localization_types.py +11 -11
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/comment_linebreaks.py +4 -4
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/comment_similarity.py +7 -4
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/duplicate_keys.py +8 -5
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/has_comments.py +12 -6
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/has_value.py +5 -5
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/invalid_tokens.py +10 -5
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/key_length.py +17 -7
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/objectivec_alternative_tokens.py +9 -4
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/placeholder_token_explanation.py +11 -5
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/swift_interpolation.py +9 -4
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/test_case.py +9 -6
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/token_matching.py +7 -4
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/token_position_identifiers.py +17 -7
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/utility_types.py +3 -5
- {localizationkit-2.0.0 → localizationkit-3.0.1}/pyproject.toml +7 -7
- {localizationkit-2.0.0 → localizationkit-3.0.1}/LICENSE +0 -0
- {localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/__init__.py +0 -0
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: localizationkit
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: String localization tests
|
|
5
5
|
Home-page: https://github.com/Microsoft/localizationkit
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: localization,strings,tests
|
|
8
8
|
Author: Dale Myers
|
|
9
9
|
Author-email: dalemy@microsoft.com
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.11,<4.0
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
21
|
Classifier: Topic :: Software Development
|
|
20
22
|
Classifier: Topic :: Software Development :: Testing
|
|
21
23
|
Classifier: Topic :: Utilities
|
|
@@ -98,7 +100,7 @@ for result in results:
|
|
|
98
100
|
print("The following test failed:", result.name)
|
|
99
101
|
print("Failures encountered:")
|
|
100
102
|
for violation in result.violations:
|
|
101
|
-
print(violation)
|
|
103
|
+
print(violation[0]) # The other value in the tuple is the language code
|
|
102
104
|
```
|
|
103
105
|
|
|
104
106
|
### Not running the tests
|
|
@@ -73,7 +73,7 @@ for result in results:
|
|
|
73
73
|
print("The following test failed:", result.name)
|
|
74
74
|
print("Failures encountered:")
|
|
75
75
|
for violation in result.violations:
|
|
76
|
-
print(violation)
|
|
76
|
+
print(violation[0]) # The other value in the tuple is the language code
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
### Not running the tests
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""Toolkit for validation of localized strings."""
|
|
2
2
|
|
|
3
|
-
import abc
|
|
4
|
-
import difflib
|
|
5
3
|
import inspect
|
|
6
|
-
from typing import
|
|
4
|
+
from typing import Type
|
|
7
5
|
|
|
8
6
|
from localizationkit.configuration import Configuration
|
|
7
|
+
from localizationkit.exceptions import LocalizationKitException
|
|
9
8
|
from localizationkit.localization_types import LocalizedCollection, LocalizedString
|
|
10
9
|
from localizationkit.utility_types import TestResult
|
|
11
10
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
@@ -13,7 +12,7 @@ from localizationkit.tests.test_case import LocalizationTestCase
|
|
|
13
12
|
from localizationkit import tests
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def _find_tests() ->
|
|
15
|
+
def _find_tests() -> list[Type[LocalizationTestCase]]:
|
|
17
16
|
"""Find all the tests."""
|
|
18
17
|
|
|
19
18
|
test_module_names = [
|
|
@@ -21,8 +20,8 @@ def _find_tests() -> List[Type[LocalizationTestCase]]:
|
|
|
21
20
|
]
|
|
22
21
|
test_modules = [getattr(tests, name) for name in test_module_names]
|
|
23
22
|
|
|
24
|
-
names_seen:
|
|
25
|
-
test_classes:
|
|
23
|
+
names_seen: set[str] = set()
|
|
24
|
+
test_classes: list[Type[LocalizationTestCase]] = []
|
|
26
25
|
|
|
27
26
|
for module in test_modules:
|
|
28
27
|
|
|
@@ -40,7 +39,7 @@ def _find_tests() -> List[Type[LocalizationTestCase]]:
|
|
|
40
39
|
continue
|
|
41
40
|
|
|
42
41
|
if reference.__name__ in names_seen:
|
|
43
|
-
raise
|
|
42
|
+
raise LocalizationKitException(
|
|
44
43
|
"At least 2 classes exist with the same name: " + reference.__name__
|
|
45
44
|
)
|
|
46
45
|
|
|
@@ -49,7 +48,7 @@ def _find_tests() -> List[Type[LocalizationTestCase]]:
|
|
|
49
48
|
return test_classes
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
def run_tests(configuration: Configuration, collection: LocalizedCollection) ->
|
|
51
|
+
def run_tests(configuration: Configuration, collection: LocalizedCollection) -> list[TestResult]:
|
|
53
52
|
"""Run all tests."""
|
|
54
53
|
|
|
55
54
|
test_classes = _find_tests()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Configuration for localization tests."""
|
|
2
2
|
|
|
3
|
-
from typing import Any, cast
|
|
3
|
+
from typing import Any, cast
|
|
4
4
|
|
|
5
5
|
import toml
|
|
6
6
|
|
|
@@ -8,9 +8,9 @@ import toml
|
|
|
8
8
|
class Configuration:
|
|
9
9
|
"""Holds all the configuration data."""
|
|
10
10
|
|
|
11
|
-
raw_config:
|
|
11
|
+
raw_config: dict[str, Any]
|
|
12
12
|
|
|
13
|
-
def __init__(self, raw_config:
|
|
13
|
+
def __init__(self, raw_config: dict[str, Any]) -> None:
|
|
14
14
|
self.raw_config = raw_config
|
|
15
15
|
|
|
16
16
|
def default_language(self) -> str:
|
|
@@ -22,21 +22,21 @@ class Configuration:
|
|
|
22
22
|
"""
|
|
23
23
|
return self.raw_config.get("default_language", "en")
|
|
24
24
|
|
|
25
|
-
def blacklist(self) ->
|
|
25
|
+
def blacklist(self) -> list[str]:
|
|
26
26
|
"""Return the list of blacklisted tests.
|
|
27
27
|
|
|
28
28
|
:returns: The blacklisted tests
|
|
29
29
|
"""
|
|
30
30
|
return self.raw_config.get("blacklist", [])
|
|
31
31
|
|
|
32
|
-
def opt_in(self) ->
|
|
32
|
+
def opt_in(self) -> list[str]:
|
|
33
33
|
"""Return the list of opted in tests.
|
|
34
34
|
|
|
35
35
|
:returns: The opted in tests
|
|
36
36
|
"""
|
|
37
37
|
return self.raw_config.get("opt_in", [])
|
|
38
38
|
|
|
39
|
-
def get_test_preferences(self, name: str) ->
|
|
39
|
+
def get_test_preferences(self, name: str) -> dict[str, Any]:
|
|
40
40
|
"""Get the preferences for a given test.
|
|
41
41
|
|
|
42
42
|
:param name: The name of the test
|
|
@@ -55,4 +55,4 @@ class Configuration:
|
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
57
|
with open(file_path, encoding="utf-8") as config_file:
|
|
58
|
-
return Configuration(cast(
|
|
58
|
+
return Configuration(cast(dict[str, Any], toml.load(config_file)))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Exception types."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LocalizationKitException(Exception):
|
|
5
|
+
"""Represents an exception from this library."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DuplicateTestException(LocalizationKitException):
|
|
9
|
+
"""Represents an exception where multiple tests with the same name appear."""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Types for localization."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import ClassVar,
|
|
4
|
+
from typing import ClassVar, Pattern
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class LocalizedString:
|
|
@@ -13,9 +13,9 @@ class LocalizedString:
|
|
|
13
13
|
:param language_code: The language code for the language the string is in
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
_TOKEN_REGEX: ClassVar[
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
_TOKEN_REGEX: ClassVar[str] = (
|
|
17
|
+
r"(%(?:[0-9]+\$)?[0-9]*\.?[0-9]*(?:h|hh|l|ll|q|L|z|t|j){0,2}[dDuUxXoOfFeEgGcCsSaAp@])"
|
|
18
|
+
)
|
|
19
19
|
_TOKEN_PATTERN: ClassVar[Pattern] = re.compile(_TOKEN_REGEX, flags=re.DOTALL)
|
|
20
20
|
|
|
21
21
|
key: str
|
|
@@ -29,14 +29,14 @@ class LocalizedString:
|
|
|
29
29
|
self.comment = comment
|
|
30
30
|
self.language_code = language_code
|
|
31
31
|
|
|
32
|
-
def tokens(self) ->
|
|
32
|
+
def tokens(self) -> list[str]:
|
|
33
33
|
"""Find and return the tokens in the string.
|
|
34
34
|
|
|
35
35
|
:returns: The list of tokens in the string
|
|
36
36
|
"""
|
|
37
37
|
return LocalizedString._TOKEN_PATTERN.findall(self.value)
|
|
38
38
|
|
|
39
|
-
def comment_tokens(self) ->
|
|
39
|
+
def comment_tokens(self) -> list[str]:
|
|
40
40
|
"""Find and return the tokens in the comment string.
|
|
41
41
|
|
|
42
42
|
:returns: The list of tokens in the comment string
|
|
@@ -64,12 +64,12 @@ class LocalizedCollection:
|
|
|
64
64
|
:param localized_strings: The list of localized strings in the collection
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
|
-
localized_strings:
|
|
67
|
+
localized_strings: list[LocalizedString]
|
|
68
68
|
|
|
69
|
-
def __init__(self, localized_strings:
|
|
69
|
+
def __init__(self, localized_strings: list[LocalizedString]) -> None:
|
|
70
70
|
self.localized_strings = localized_strings
|
|
71
71
|
|
|
72
|
-
def strings_for_key(self, key: str) ->
|
|
72
|
+
def strings_for_key(self, key: str) -> list[LocalizedString]:
|
|
73
73
|
"""Return all the strings matching the key
|
|
74
74
|
|
|
75
75
|
i.e. This will have a list of strings with identical keys, but different
|
|
@@ -79,7 +79,7 @@ class LocalizedCollection:
|
|
|
79
79
|
"""
|
|
80
80
|
return [string for string in self.localized_strings if string.key == key]
|
|
81
81
|
|
|
82
|
-
def strings_for_language(self, language_code: str) ->
|
|
82
|
+
def strings_for_language(self, language_code: str) -> list[LocalizedString]:
|
|
83
83
|
"""Return all the strings matching the language code
|
|
84
84
|
|
|
85
85
|
:returns: The list of strings with the matching language code
|
|
@@ -88,7 +88,7 @@ class LocalizedCollection:
|
|
|
88
88
|
string for string in self.localized_strings if string.language_code == language_code
|
|
89
89
|
]
|
|
90
90
|
|
|
91
|
-
def languages(self) ->
|
|
91
|
+
def languages(self) -> list[str]:
|
|
92
92
|
"""Return the list of languages in the collection.
|
|
93
93
|
|
|
94
94
|
:returns: The list of langauges in the collection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Checks for comments."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,14 +13,14 @@ class CommentLinebreaks(LocalizationTestCase):
|
|
|
13
13
|
return "comment_linebreaks"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {}
|
|
18
18
|
|
|
19
19
|
@classmethod
|
|
20
20
|
def is_opt_in(cls) -> bool:
|
|
21
21
|
return True
|
|
22
22
|
|
|
23
|
-
def run_test(self) ->
|
|
23
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
24
24
|
violations = []
|
|
25
25
|
|
|
26
26
|
for string in self.collection.strings_for_language(self.configuration.default_language()):
|
|
@@ -29,6 +29,6 @@ class CommentLinebreaks(LocalizationTestCase):
|
|
|
29
29
|
continue
|
|
30
30
|
|
|
31
31
|
if "\n" in string.comment or "\r" in string.comment:
|
|
32
|
-
violations.append(f"Comment contains linebreaks: {string}")
|
|
32
|
+
violations.append((f"Comment contains linebreaks: {string}", string.language_code))
|
|
33
33
|
|
|
34
34
|
return violations
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Comment similarity."""
|
|
2
2
|
|
|
3
3
|
import difflib
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
7
7
|
|
|
@@ -14,10 +14,10 @@ class CheckCommentSimilarity(LocalizationTestCase):
|
|
|
14
14
|
return "comment_similarity"
|
|
15
15
|
|
|
16
16
|
@classmethod
|
|
17
|
-
def default_settings(cls) ->
|
|
17
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
18
18
|
return {"maximum_similarity_ratio": 0.5}
|
|
19
19
|
|
|
20
|
-
def run_test(self) ->
|
|
20
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
21
21
|
|
|
22
22
|
maximum_similarity_ratio = self.get_setting("maximum_similarity_ratio")
|
|
23
23
|
|
|
@@ -31,7 +31,10 @@ class CheckCommentSimilarity(LocalizationTestCase):
|
|
|
31
31
|
|
|
32
32
|
if similarity > maximum_similarity_ratio:
|
|
33
33
|
violations.append(
|
|
34
|
-
|
|
34
|
+
(
|
|
35
|
+
f"Value and comment exceeded similarity ratio of {maximum_similarity_ratio}: {string}",
|
|
36
|
+
string.language_code,
|
|
37
|
+
)
|
|
35
38
|
)
|
|
36
39
|
continue
|
|
37
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Duplicate keys."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,10 +13,10 @@ class DuplicateKeys(LocalizationTestCase):
|
|
|
13
13
|
return "duplicate_keys"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {"all_languages": False}
|
|
18
18
|
|
|
19
|
-
def run_test(self) ->
|
|
19
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
20
20
|
|
|
21
21
|
violations = []
|
|
22
22
|
|
|
@@ -29,13 +29,16 @@ class DuplicateKeys(LocalizationTestCase):
|
|
|
29
29
|
|
|
30
30
|
for language in languages_to_check:
|
|
31
31
|
|
|
32
|
-
keys:
|
|
32
|
+
keys: set[str] = set()
|
|
33
33
|
|
|
34
34
|
for string in self.collection.strings_for_language(language):
|
|
35
35
|
|
|
36
36
|
if string.key in keys:
|
|
37
37
|
violations.append(
|
|
38
|
-
|
|
38
|
+
(
|
|
39
|
+
f"The key '{string.key}' appears for multiple strings. e.g. {string}",
|
|
40
|
+
string.language_code,
|
|
41
|
+
)
|
|
39
42
|
)
|
|
40
43
|
continue
|
|
41
44
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Checks for comments."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,10 +13,10 @@ class HasComments(LocalizationTestCase):
|
|
|
13
13
|
return "has_comments"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {"minimum_comment_length": 30, "minimum_comment_words": 10}
|
|
18
18
|
|
|
19
|
-
def run_test(self) ->
|
|
19
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
20
20
|
|
|
21
21
|
minimum_comment_length = self.get_setting("minimum_comment_length")
|
|
22
22
|
minimum_comment_words = self.get_setting("minimum_comment_words")
|
|
@@ -26,12 +26,15 @@ class HasComments(LocalizationTestCase):
|
|
|
26
26
|
for string in self.collection.strings_for_language(self.configuration.default_language()):
|
|
27
27
|
|
|
28
28
|
if string.comment is None:
|
|
29
|
-
violations.append(f"Comment was empty: {string}")
|
|
29
|
+
violations.append((f"Comment was empty: {string}", string.language_code))
|
|
30
30
|
continue
|
|
31
31
|
|
|
32
32
|
if minimum_comment_length >= 0 and len(string.comment) < minimum_comment_length:
|
|
33
33
|
violations.append(
|
|
34
|
-
|
|
34
|
+
(
|
|
35
|
+
f"Comment did not meet minimum length of {minimum_comment_length}: {string}",
|
|
36
|
+
string.language_code,
|
|
37
|
+
)
|
|
35
38
|
)
|
|
36
39
|
continue
|
|
37
40
|
|
|
@@ -40,7 +43,10 @@ class HasComments(LocalizationTestCase):
|
|
|
40
43
|
and len(string.comment.split(" ")) < minimum_comment_words
|
|
41
44
|
):
|
|
42
45
|
violations.append(
|
|
43
|
-
|
|
46
|
+
(
|
|
47
|
+
f"Comment did not meet minimum word count of {minimum_comment_words}: {string}",
|
|
48
|
+
string.language_code,
|
|
49
|
+
)
|
|
44
50
|
)
|
|
45
51
|
continue
|
|
46
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Checks that the value is set."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,12 +13,12 @@ class HasValue(LocalizationTestCase):
|
|
|
13
13
|
return "has_value"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {"default_language_only": False}
|
|
18
18
|
|
|
19
|
-
def run_test(self) ->
|
|
19
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
20
20
|
|
|
21
|
-
violations:
|
|
21
|
+
violations: list[tuple[str, str]] = []
|
|
22
22
|
|
|
23
23
|
if self.get_setting("default_language_only"):
|
|
24
24
|
test_collection = self.collection.strings_for_language(
|
|
@@ -29,7 +29,7 @@ class HasValue(LocalizationTestCase):
|
|
|
29
29
|
|
|
30
30
|
for string in test_collection:
|
|
31
31
|
if string.value is None or len(string.value) == 0:
|
|
32
|
-
violations.append(f"Value was empty: {string}")
|
|
32
|
+
violations.append((f"Value was empty: {string}", string.language_code))
|
|
33
33
|
continue
|
|
34
34
|
|
|
35
35
|
return violations
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Invalid tokens."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
7
7
|
|
|
@@ -14,20 +14,25 @@ class InvalidTokens(LocalizationTestCase):
|
|
|
14
14
|
return "invalid_tokens"
|
|
15
15
|
|
|
16
16
|
@classmethod
|
|
17
|
-
def default_settings(cls) ->
|
|
17
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
18
18
|
return {}
|
|
19
19
|
|
|
20
|
-
def run_test(self) ->
|
|
20
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
21
21
|
|
|
22
22
|
violations = []
|
|
23
23
|
|
|
24
|
-
invalid_token_pattern = re.compile(r"(%[^@%\.a-
|
|
24
|
+
invalid_token_pattern = re.compile(r"(%[^@%\.a-zA-Z0-9 ]+)", flags=re.DOTALL)
|
|
25
25
|
|
|
26
26
|
for string in self.collection.localized_strings:
|
|
27
27
|
matches = invalid_token_pattern.findall(string.value)
|
|
28
28
|
|
|
29
29
|
# Any matches are a bad thing
|
|
30
30
|
if matches and len(matches) > 0:
|
|
31
|
-
violations.append(
|
|
31
|
+
violations.append(
|
|
32
|
+
(
|
|
33
|
+
f"Translation contains invalid tokens ({matches}): {string}",
|
|
34
|
+
string.language_code,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
32
37
|
|
|
33
38
|
return violations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Checks key length."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,15 +13,15 @@ class KeyLength(LocalizationTestCase):
|
|
|
13
13
|
return "key_length"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {"minimum": -1, "maximum": -1}
|
|
18
18
|
|
|
19
|
-
def run_test(self) ->
|
|
19
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
20
20
|
|
|
21
21
|
minimum_length = self.get_setting("minimum")
|
|
22
22
|
maximum_length = self.get_setting("maximum")
|
|
23
23
|
|
|
24
|
-
violations:
|
|
24
|
+
violations: list[tuple[str, str]] = []
|
|
25
25
|
|
|
26
26
|
# Short circuit for the non-bound case
|
|
27
27
|
if minimum_length < 0 and maximum_length < 0:
|
|
@@ -32,13 +32,23 @@ class KeyLength(LocalizationTestCase):
|
|
|
32
32
|
key = string.key
|
|
33
33
|
|
|
34
34
|
if key is None:
|
|
35
|
-
violations.append(f"Key was empty: {string}")
|
|
35
|
+
violations.append((f"Key was empty: {string}", string.language_code))
|
|
36
36
|
continue
|
|
37
37
|
|
|
38
38
|
if len(key) < minimum_length >= 0:
|
|
39
|
-
violations.append(
|
|
39
|
+
violations.append(
|
|
40
|
+
(
|
|
41
|
+
f"Key is shorter than minimum of {minimum_length}: {string}",
|
|
42
|
+
string.language_code,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
40
45
|
|
|
41
46
|
if len(key) > maximum_length >= 0:
|
|
42
|
-
violations.append(
|
|
47
|
+
violations.append(
|
|
48
|
+
(
|
|
49
|
+
f"Key is longer than maximum of {maximum_length}: {string}",
|
|
50
|
+
string.language_code,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
43
53
|
|
|
44
54
|
return violations
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Objective-C alternative tokens."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
7
7
|
|
|
@@ -19,14 +19,14 @@ class ObjectivecAlternativeTokens(LocalizationTestCase):
|
|
|
19
19
|
return "objectivec_alternative_tokens"
|
|
20
20
|
|
|
21
21
|
@classmethod
|
|
22
|
-
def default_settings(cls) ->
|
|
22
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
23
23
|
return {}
|
|
24
24
|
|
|
25
25
|
@classmethod
|
|
26
26
|
def is_opt_in(cls) -> bool:
|
|
27
27
|
return True
|
|
28
28
|
|
|
29
|
-
def run_test(self) ->
|
|
29
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
30
30
|
|
|
31
31
|
violations = []
|
|
32
32
|
|
|
@@ -35,7 +35,12 @@ class ObjectivecAlternativeTokens(LocalizationTestCase):
|
|
|
35
35
|
for string in self.collection.localized_strings:
|
|
36
36
|
|
|
37
37
|
if pattern.findall(string.value):
|
|
38
|
-
violations.append(
|
|
38
|
+
violations.append(
|
|
39
|
+
(
|
|
40
|
+
f"Objective-C alternative tokens were found in string: {string}",
|
|
41
|
+
string.language_code,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
39
44
|
continue
|
|
40
45
|
|
|
41
46
|
return violations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Placeholder token explanation."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,14 +13,14 @@ class PlaceholderTokenExplanation(LocalizationTestCase):
|
|
|
13
13
|
return "placeholder_token_explanation"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {}
|
|
18
18
|
|
|
19
19
|
@classmethod
|
|
20
20
|
def is_opt_in(cls) -> bool:
|
|
21
21
|
return True
|
|
22
22
|
|
|
23
|
-
def run_test(self) ->
|
|
23
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
24
24
|
|
|
25
25
|
violations = []
|
|
26
26
|
|
|
@@ -33,12 +33,18 @@ class PlaceholderTokenExplanation(LocalizationTestCase):
|
|
|
33
33
|
|
|
34
34
|
if len(extra_in_tokens) != 0:
|
|
35
35
|
violations.append(
|
|
36
|
-
|
|
36
|
+
(
|
|
37
|
+
f"Tokens are not described in the comment ({extra_in_tokens}): {string}",
|
|
38
|
+
string.language_code,
|
|
39
|
+
)
|
|
37
40
|
)
|
|
38
41
|
|
|
39
42
|
if len(extra_in_comments) != 0:
|
|
40
43
|
violations.append(
|
|
41
|
-
|
|
44
|
+
(
|
|
45
|
+
f"Extra tokens appear in the comment ({extra_in_comments}): {string}",
|
|
46
|
+
string.language_code,
|
|
47
|
+
)
|
|
42
48
|
)
|
|
43
49
|
|
|
44
50
|
return violations
|
{localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/swift_interpolation.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Swift interpolation."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
7
7
|
|
|
@@ -14,14 +14,14 @@ class SwiftInterpolation(LocalizationTestCase):
|
|
|
14
14
|
return "swift_interpolation"
|
|
15
15
|
|
|
16
16
|
@classmethod
|
|
17
|
-
def default_settings(cls) ->
|
|
17
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
18
18
|
return {}
|
|
19
19
|
|
|
20
20
|
@classmethod
|
|
21
21
|
def is_opt_in(cls) -> bool:
|
|
22
22
|
return True
|
|
23
23
|
|
|
24
|
-
def run_test(self) ->
|
|
24
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
25
25
|
|
|
26
26
|
violations = []
|
|
27
27
|
|
|
@@ -30,7 +30,12 @@ class SwiftInterpolation(LocalizationTestCase):
|
|
|
30
30
|
for string in self.collection.localized_strings:
|
|
31
31
|
|
|
32
32
|
if pattern.findall(string.value):
|
|
33
|
-
violations.append(
|
|
33
|
+
violations.append(
|
|
34
|
+
(
|
|
35
|
+
f"Swift string interpolation found in string: {string}",
|
|
36
|
+
string.language_code,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
34
39
|
continue
|
|
35
40
|
|
|
36
41
|
return violations
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Base type for localization."""
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from localizationkit.configuration import Configuration
|
|
7
|
+
from localizationkit.exceptions import LocalizationKitException
|
|
7
8
|
from localizationkit.localization_types import LocalizedCollection
|
|
8
9
|
from localizationkit.utility_types import TestResult
|
|
9
10
|
|
|
@@ -18,7 +19,7 @@ class LocalizationTestCase(abc.ABC):
|
|
|
18
19
|
|
|
19
20
|
configuration: Configuration
|
|
20
21
|
collection: LocalizedCollection
|
|
21
|
-
test_settings:
|
|
22
|
+
test_settings: dict[str, Any]
|
|
22
23
|
|
|
23
24
|
def __init__(self, configuration: Configuration, collection: LocalizedCollection):
|
|
24
25
|
self.configuration = configuration
|
|
@@ -45,7 +46,7 @@ class LocalizationTestCase(abc.ABC):
|
|
|
45
46
|
|
|
46
47
|
@classmethod
|
|
47
48
|
@abc.abstractmethod
|
|
48
|
-
def default_settings(cls) ->
|
|
49
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
49
50
|
"""Get the default settings for the test.
|
|
50
51
|
|
|
51
52
|
:returns: The default settings for the test
|
|
@@ -64,7 +65,7 @@ class LocalizationTestCase(abc.ABC):
|
|
|
64
65
|
return False
|
|
65
66
|
|
|
66
67
|
@abc.abstractmethod
|
|
67
|
-
def run_test(self) ->
|
|
68
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
68
69
|
"""Run the test
|
|
69
70
|
|
|
70
71
|
:returns: The list of violations encountered
|
|
@@ -80,11 +81,13 @@ class LocalizationTestCase(abc.ABC):
|
|
|
80
81
|
violations = self.run_test()
|
|
81
82
|
|
|
82
83
|
if violations is None:
|
|
83
|
-
raise
|
|
84
|
+
raise LocalizationKitException(
|
|
85
|
+
f"Failed to get exceptions from test: {self.__class__.name()}"
|
|
86
|
+
)
|
|
84
87
|
|
|
85
88
|
if len(violations):
|
|
86
89
|
return TestResult.failure(self.__class__.name(), violations)
|
|
87
90
|
|
|
88
91
|
return TestResult.success(self.__class__.name())
|
|
89
92
|
except Exception as ex:
|
|
90
|
-
return TestResult.failure(self.__class__.name(), [str(ex)])
|
|
93
|
+
return TestResult.failure(self.__class__.name(), [(str(ex), "unknown")])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Token matching."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,10 +13,10 @@ class TokenMatching(LocalizationTestCase):
|
|
|
13
13
|
return "token_matching"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {"allow_missing_defaults": False}
|
|
18
18
|
|
|
19
|
-
def run_test(self) ->
|
|
19
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
20
20
|
|
|
21
21
|
violations = []
|
|
22
22
|
|
|
@@ -46,7 +46,10 @@ class TokenMatching(LocalizationTestCase):
|
|
|
46
46
|
translation_tokens = sorted(string.tokens())
|
|
47
47
|
if default_tokens != translation_tokens:
|
|
48
48
|
violations.append(
|
|
49
|
-
|
|
49
|
+
(
|
|
50
|
+
f"The tokens for {language_code} do not match the default language: {string.key}",
|
|
51
|
+
string.language_code,
|
|
52
|
+
)
|
|
50
53
|
)
|
|
51
54
|
|
|
52
55
|
return violations
|
{localizationkit-2.0.0 → localizationkit-3.0.1}/localizationkit/tests/token_position_identifiers.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Token position identifiers."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
6
|
|
|
@@ -13,10 +13,10 @@ class TokenPositionIdentifiers(LocalizationTestCase):
|
|
|
13
13
|
return "token_position_identifiers"
|
|
14
14
|
|
|
15
15
|
@classmethod
|
|
16
|
-
def default_settings(cls) ->
|
|
16
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
17
17
|
return {"always": False}
|
|
18
18
|
|
|
19
|
-
def run_test(self) ->
|
|
19
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
20
20
|
|
|
21
21
|
violations = []
|
|
22
22
|
|
|
@@ -34,23 +34,33 @@ class TokenPositionIdentifiers(LocalizationTestCase):
|
|
|
34
34
|
continue
|
|
35
35
|
|
|
36
36
|
# Track the positions we've seen
|
|
37
|
-
positional_tokens:
|
|
37
|
+
positional_tokens: set[int] = set()
|
|
38
38
|
|
|
39
39
|
for token in tokens:
|
|
40
40
|
if "$" not in token:
|
|
41
|
-
violations.append(
|
|
41
|
+
violations.append(
|
|
42
|
+
(
|
|
43
|
+
f"String missing positional tokens: {string}",
|
|
44
|
+
string.language_code,
|
|
45
|
+
)
|
|
46
|
+
)
|
|
42
47
|
continue
|
|
43
48
|
|
|
44
49
|
position = int((token.split("$")[0]).replace("%", ""))
|
|
45
50
|
|
|
46
51
|
if position in positional_tokens:
|
|
47
|
-
violations.append(f"Duplicate token position: {string}")
|
|
52
|
+
violations.append((f"Duplicate token position: {string}", string.language_code))
|
|
48
53
|
continue
|
|
49
54
|
|
|
50
55
|
positional_tokens.add(position)
|
|
51
56
|
|
|
52
57
|
for i in range(1, len(tokens) + 1):
|
|
53
58
|
if i not in positional_tokens:
|
|
54
|
-
violations.append(
|
|
59
|
+
violations.append(
|
|
60
|
+
(
|
|
61
|
+
f"Token position index skipped: {string}",
|
|
62
|
+
string.language_code,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
55
65
|
|
|
56
66
|
return violations
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Utility types for tests."""
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
class TestResult:
|
|
7
5
|
"""A simple result type
|
|
@@ -12,9 +10,9 @@ class TestResult:
|
|
|
12
10
|
|
|
13
11
|
_success: bool
|
|
14
12
|
name: str
|
|
15
|
-
violations:
|
|
13
|
+
violations: list[tuple[str, str]] | None
|
|
16
14
|
|
|
17
|
-
def __init__(self, success: bool, name: str, violations:
|
|
15
|
+
def __init__(self, success: bool, name: str, violations: list[tuple[str, str]] | None):
|
|
18
16
|
self._success = success
|
|
19
17
|
self.name = name
|
|
20
18
|
self.violations = violations
|
|
@@ -37,7 +35,7 @@ class TestResult:
|
|
|
37
35
|
return TestResult(True, name, None)
|
|
38
36
|
|
|
39
37
|
@staticmethod
|
|
40
|
-
def failure(name: str, violations:
|
|
38
|
+
def failure(name: str, violations: list[tuple[str, str]] | None) -> "TestResult":
|
|
41
39
|
"""Create a new failure result.
|
|
42
40
|
|
|
43
41
|
:param name: The name of the test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "localizationkit"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.0.1"
|
|
4
4
|
description = "String localization tests"
|
|
5
5
|
|
|
6
6
|
license = "MIT"
|
|
@@ -30,15 +30,15 @@ classifiers = [
|
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[tool.poetry.dependencies]
|
|
33
|
-
python = "^3.
|
|
33
|
+
python = "^3.11"
|
|
34
34
|
toml = "^0.10.2"
|
|
35
35
|
|
|
36
36
|
[tool.poetry.dev-dependencies]
|
|
37
|
-
black = "
|
|
38
|
-
mypy = "
|
|
39
|
-
pylint = "
|
|
40
|
-
pytest = "^
|
|
41
|
-
pytest-cov = "^
|
|
37
|
+
black = "25.1.0"
|
|
38
|
+
mypy = "1.15.0"
|
|
39
|
+
pylint = "3.3.4"
|
|
40
|
+
pytest = "^8.1.1"
|
|
41
|
+
pytest-cov = "^4.1.0"
|
|
42
42
|
types-toml = "^0.10.8.7"
|
|
43
43
|
|
|
44
44
|
|
|
File without changes
|
|
File without changes
|