localizationkit 1.0.1__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.
Files changed (24) hide show
  1. {localizationkit-1.0.1 → localizationkit-3.0.1}/PKG-INFO +7 -5
  2. {localizationkit-1.0.1 → localizationkit-3.0.1}/README.md +1 -1
  3. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/__init__.py +7 -8
  4. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/configuration.py +7 -7
  5. localizationkit-3.0.1/localizationkit/exceptions.py +9 -0
  6. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/localization_types.py +11 -11
  7. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/comment_linebreaks.py +4 -4
  8. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/comment_similarity.py +7 -4
  9. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/duplicate_keys.py +8 -5
  10. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/has_comments.py +12 -6
  11. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/has_value.py +5 -5
  12. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/invalid_tokens.py +10 -5
  13. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/key_length.py +17 -7
  14. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/objectivec_alternative_tokens.py +9 -4
  15. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/placeholder_token_explanation.py +11 -5
  16. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/swift_interpolation.py +9 -4
  17. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/test_case.py +9 -6
  18. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/token_matching.py +7 -4
  19. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/token_position_identifiers.py +17 -7
  20. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/utility_types.py +3 -5
  21. {localizationkit-1.0.1 → localizationkit-3.0.1}/pyproject.toml +12 -12
  22. localizationkit-1.0.1/setup.py +0 -30
  23. {localizationkit-1.0.1 → localizationkit-3.0.1}/LICENSE +0 -0
  24. {localizationkit-1.0.1 → localizationkit-3.0.1}/localizationkit/tests/__init__.py +0 -0
@@ -1,25 +1,27 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: localizationkit
3
- Version: 1.0.1
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.7.2,<4.0.0
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
15
18
  Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.7
17
19
  Classifier: Programming Language :: Python :: 3.8
18
20
  Classifier: Programming Language :: Python :: 3.9
19
21
  Classifier: Topic :: Software Development
20
22
  Classifier: Topic :: Software Development :: Testing
21
23
  Classifier: Topic :: Utilities
22
- Requires-Dist: toml (>=0.10.0,<0.11.0)
24
+ Requires-Dist: toml (>=0.10.2,<0.11.0)
23
25
  Project-URL: Repository, https://github.com/Microsoft/localizationkit
24
26
  Description-Content-Type: text/markdown
25
27
 
@@ -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 Any, Dict, List, Optional, Set, Type
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() -> List[Type[LocalizationTestCase]]:
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: Set[str] = set()
25
- test_classes: List[Type[LocalizationTestCase]] = []
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 Exception(
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) -> List[TestResult]:
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, Dict, List
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: Dict[str, Any]
11
+ raw_config: dict[str, Any]
12
12
 
13
- def __init__(self, raw_config: Dict[str, Any]) -> None:
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) -> List[str]:
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) -> List[str]:
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) -> Dict[str, Any]:
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(Dict[str, Any], toml.load(config_file)))
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, List, Pattern
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
- str
18
- ] = r"(%(?:[0-9]+\$)?[0-9]*\.?[0-9]*(?:h|hh|l|ll|q|L|z|t|j){0,2}[dDuUxXoOfFeEgGcCsSaAp@])"
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) -> List[str]:
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) -> List[str]:
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: List[LocalizedString]
67
+ localized_strings: list[LocalizedString]
68
68
 
69
- def __init__(self, localized_strings: List[LocalizedString]) -> None:
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) -> List[LocalizedString]:
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) -> List[LocalizedString]:
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) -> List[str]:
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, Dict, List
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) -> Dict[str, Any]:
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) -> List[str]:
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, Dict, List
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) -> Dict[str, Any]:
17
+ def default_settings(cls) -> dict[str, Any]:
18
18
  return {"maximum_similarity_ratio": 0.5}
19
19
 
20
- def run_test(self) -> List[str]:
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
- f"Value and comment exceeded similarity ratio of {maximum_similarity_ratio}: {string}"
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, Dict, List, Set
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) -> Dict[str, Any]:
16
+ def default_settings(cls) -> dict[str, Any]:
17
17
  return {"all_languages": False}
18
18
 
19
- def run_test(self) -> List[str]:
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: Set[str] = set()
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
- f"The key '{string.key}' appears for multiple strings. e.g. {string}"
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, Dict, List
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) -> Dict[str, Any]:
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) -> List[str]:
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
- f"Comment did not meet minimum length of {minimum_comment_length}: {string}"
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
- f"Comment did not meet minimum word count of {minimum_comment_words}: {string}"
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, Dict, List
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) -> Dict[str, Any]:
16
+ def default_settings(cls) -> dict[str, Any]:
17
17
  return {"default_language_only": False}
18
18
 
19
- def run_test(self) -> List[str]:
19
+ def run_test(self) -> list[tuple[str, str]]:
20
20
 
21
- violations: List[str] = []
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, Dict, List
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) -> Dict[str, Any]:
17
+ def default_settings(cls) -> dict[str, Any]:
18
18
  return {}
19
19
 
20
- def run_test(self) -> List[str]:
20
+ def run_test(self) -> list[tuple[str, str]]:
21
21
 
22
22
  violations = []
23
23
 
24
- invalid_token_pattern = re.compile(r"(%[^@%\.a-z0-9 ]+)", flags=re.DOTALL)
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(f"Translation contains invalid tokens ({matches}): {string}")
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, Dict, List
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) -> Dict[str, Any]:
16
+ def default_settings(cls) -> dict[str, Any]:
17
17
  return {"minimum": -1, "maximum": -1}
18
18
 
19
- def run_test(self) -> List[str]:
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: List[str] = []
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(f"Key is shorter than minimum of {minimum_length}: {string}")
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(f"Key is longer than maximum of {maximum_length}: {string}")
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, Dict, List
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) -> Dict[str, Any]:
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) -> List[str]:
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(f"Objective-C alternative tokens were found in string: {string}")
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, Dict, List
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) -> Dict[str, Any]:
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) -> List[str]:
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
- f"Tokens are not described in the comment ({extra_in_tokens}): {string}"
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
- f"Extra tokens appear in the comment ({extra_in_comments}): {string}"
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
@@ -1,7 +1,7 @@
1
1
  """Swift interpolation."""
2
2
 
3
3
  import re
4
- from typing import Any, Dict, List
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) -> Dict[str, Any]:
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) -> List[str]:
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(f"Swift string interpolation found in string: {string}")
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, Dict, List
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: Dict[str, Any]
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) -> Dict[str, Any]:
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) -> List[str]:
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 Exception(f"Failed to get exceptions from test: {self.__class__.name()}")
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, Dict, List
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) -> Dict[str, Any]:
16
+ def default_settings(cls) -> dict[str, Any]:
17
17
  return {"allow_missing_defaults": False}
18
18
 
19
- def run_test(self) -> List[str]:
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
- f"The tokens for {language_code} do not match the default language: {string.key}"
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
@@ -1,6 +1,6 @@
1
1
  """Token position identifiers."""
2
2
 
3
- from typing import Any, Dict, List, Set
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) -> Dict[str, Any]:
16
+ def default_settings(cls) -> dict[str, Any]:
17
17
  return {"always": False}
18
18
 
19
- def run_test(self) -> List[str]:
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: Set[int] = set()
37
+ positional_tokens: set[int] = set()
38
38
 
39
39
  for token in tokens:
40
40
  if "$" not in token:
41
- violations.append(f"String missing positional tokens: {string}")
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(f"Token position index skipped: {string}")
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: Optional[List[str]]
13
+ violations: list[tuple[str, str]] | None
16
14
 
17
- def __init__(self, success: bool, name: str, violations: Optional[List[str]]):
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: Optional[List[str]]) -> "TestResult":
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 = "1.0.1"
3
+ version = "3.0.1"
4
4
  description = "String localization tests"
5
5
 
6
6
  license = "MIT"
@@ -20,28 +20,28 @@ classifiers = [
20
20
  'Development Status :: 3 - Alpha',
21
21
  'Intended Audience :: Developers',
22
22
  'Programming Language :: Python :: 3',
23
- 'Programming Language :: Python :: 3.7',
24
23
  'Programming Language :: Python :: 3.8',
25
24
  'Programming Language :: Python :: 3.9',
26
25
  'Programming Language :: Python :: 3.10',
26
+ 'Programming Language :: Python :: 3.11',
27
27
  'Topic :: Software Development',
28
28
  'Topic :: Software Development :: Testing',
29
29
  'Topic :: Utilities'
30
30
  ]
31
31
 
32
32
  [tool.poetry.dependencies]
33
- python = "^3.7.2"
34
- toml = "^0.10.0"
33
+ python = "^3.11"
34
+ toml = "^0.10.2"
35
35
 
36
36
  [tool.poetry.dev-dependencies]
37
- black = "=22.6.0"
38
- mypy = "=0.971"
39
- pylint = "=2.14.5"
40
- pytest = "=7.1.2"
41
- pytest-cov = "=3.0.0"
42
- types-toml = "=0.10.8"
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
+ types-toml = "^0.10.8.7"
43
43
 
44
44
 
45
45
  [build-system]
46
- requires = ["poetry>=0.12"]
47
- build-backend = "poetry.masonry.api"
46
+ requires = ["poetry-core"]
47
+ build-backend = "poetry.core.masonry.api"
@@ -1,30 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['localizationkit', 'localizationkit.tests']
6
-
7
- package_data = \
8
- {'': ['*']}
9
-
10
- install_requires = \
11
- ['toml>=0.10.0,<0.11.0']
12
-
13
- setup_kwargs = {
14
- 'name': 'localizationkit',
15
- 'version': '1.0.1',
16
- 'description': 'String localization tests',
17
- 'long_description': '# localizationkit\n\n`localizationkit` is a toolkit for ensuring that your localized strings are the best that they can be.\n\nIncluded are tests for various things such as:\n\n* Checking that all strings have comments\n* Checking that the comments don\'t just match the value\n* Check that tokens have position specifiers\n* Check that no invalid tokens are included\n\nwith lots more to come. \n\n## Getting started\n\n### Configuration\n\nTo use the library, first off, create a configuration file that is in the TOML format. Here\'s an example:\n\n```toml\ndefault_language = "en"\n\n[has_comments]\nminimum_comment_length = 25\nminimum_comment_words = 8\n\n[token_matching]\nallow_missing_defaults = true\n\n[token_position_identifiers]\nalways = false\n```\n\nThis configuration file sets that `en` is the default language (so this is the language that will be checked for comments, etc. and all tests will run relative to it). Then it sets various settings for each test. Every instance of `[something_here]` specifies that the following settings are for that test. For example, the test `has_comments` will now make sure that not only are there comments, but that they are at least 25 characters in length and 8 words in length. \n\nYou can now load in your configuration:\n\n```python\nfrom localizationkit import Configuration\n\nconfiguration = Configuration.from_file("/path/to/config.toml")\n```\n\n### Localization Collections\n\nNow we need to prepare the strings that will go in. Here\'s how you can create an individual string:\n\n```python\nfrom localizationkit import LocalizedString\n\nmy_string = LocalizedString("My string\'s key", "My string\'s value", "My string\'s comment", "en")\n```\n\nThis creates a single string with a key, value and comment, with its language code set to `en`. Once you\'ve created some more (usually for different languages too), you can bundle them into a collection:\n\n```python\nfrom localizationkit import LocalizedCollection\n\ncollection = LocalizedCollection(list_of_my_strings)\n```\n\n### Running the tests\n\nAt this point, you are ready to run the tests:\n\n```python\nimport localizationkit\n\nresults = localizationkit.run_tests(configuration, collection)\n\nfor result in results:\n if not result.succeeded():\n print("The following test failed:", result.name)\n print("Failures encountered:")\n for violation in result.violations:\n print(violation)\n```\n\n### Not running the tests\n\nSome tests don\'t make sense for everyone. To skip a test you can add the following to your config file at the root:\n\n```toml\nblacklist = ["test_identifier_1", "test_identifier_2"]\n```\n\n# Rule documentation\n\nMost tests have configurable rules. If a rule is not specified, it will use the default instead.\n\nSome tests are opt in only. These will be marked as such.\n\n## Comment Linebreaks\n\nIdentifier: `comment_linebreaks`\nOpt-In: `true`\n\nChecks that comments for strings do not contain linebreaks. Comments which contain linebreaks can interfere with parsing in other tools such as [dotstrings](https://github.com/microsoft/dotstrings).\n\n## Comment Similarity\n\nIdentifier: `comment_similarity`\n\nChecks the similarity between a comment and the string\'s value in the default language. This is achieved via `difflib`\'s `SequenceMatcher`. More details can be found [here](https://docs.python.org/3/library/difflib.html#difflib.SequenceMatcher.ratio)\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `maximum_similarity_ratio` | float | Between 0 and 1 | 0.5 | Set the maximum similarity ratio between the comment and the string value. The higher the value, the more similar they are. The longer the string the more accurate this will be. |\n\n</details>\n\n## Duplicate Keys\n\nIdentifier: `duplicate_keys`\n\nChecks that there are no duplicate keys in the collection.\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `all_languages` | boolean | `true` or `false` | `false` | Set to `true` to check that every language has unique keys, not just the default language. |\n\n</details>\n\n## Has Comments\n\nIdentifier: `has_comments`\n\nChecks that strings have comments.\n\n_Note: Only languages that have Latin style scripts are really supported for the words check due to splitting on spaces to check._\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `minimum_comment_length` | int | Any integer | 30 | Set the minimum allowable length for a comment. Set the value to negative to not check. |\n| `minimum_comment_words` | int | Any integer | 10 | Set the minimum allowable number of words for a comment. Set the value to negative to not check. |\n\n</details>\n\n## Has Value\n\nIdentifier: `has_value`\n\nChecks that strings have values. Since any value is enough for some strings, it simply makes sure that the string isn\'t None/null and isn\'t empty.\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `default_language_only` | boolean | `true` or `false` | `false` | Set to true to only check the default language for missing values. Otherwise all languages will be checked. |\n\n</details>\n\n## Invalid Tokens\n\nIdentifier: `invalid_tokens`\n\nChecks that all format tokens in a string are valid.\n\n_Note: This check is not language specific. It only works very broadly._\n\n## Key Length\n\nIdentifier: `key_length`\n\nChecks the length of the keys.\n\n_Note: By default this test doesn\'t check anything. It needs to have parameters set to positive values to do anything._\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `minimum` | int | Any integer | -1 | Set the minimum allowable length for a key. Set the value to negative to not check. |\n| `maximum` | int | Any integer | -1 | Set the maximum allowable length for a key. Set the value to negative to not check. |\n\n</details>\n\n## Objective-C Alternative Tokens\n\nIdentifier: `objectivec_alternative_tokens`\nOpt-In: `true`\n\nChecks that strings do not contain Objective-C style alternative position tokens.\n\nObjective-C seems to be allows positional tokens of the form `%1@` rather than `%1$@`. While not illegal, it is preferred that all tokens between languages are consistent so that tools don\'t experience unexpected failures, etc.\n\n## Placeholder token explanation\n\nIdentifier: `placeholder_token_explanation`\nOpt-In: `true`\n\nChecks that if a placeholder is used in a string, the comment explicitly explains what it is replaced with.\n\nPrecondition: Each placeholder in the string and its explanation in comment is expected to follow `token_position_identifiers` rule.\n\n## Swift Interpolation\n\nIdentifier: `swift_interpolation`\nOpt-In: `true`\n\nChecks that strings do not contain Swift style interpolation values since these cannot be localized.\n\n## Token Matching\n\nIdentifier: `token_matching`\n\nChecks that the tokens in a string match across all languages. e.g. If your English string is "Hello %s" but your French string is "Bonjour", this would flag that there is a missing token in the French string.\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `allow_missing_defaults` | boolean | `true` or `false` | `false` | Due to the way that automated localization works, usually there will be a default language, and then other translations will come in over time. If a translation is deleted, it isn\'t always deleted from all languages immediately. Setting this parameter to true will allow any strings in your non-default language to be ignored if that string is missing from your default language. |\n\n</details>\n\n## Token Position Identifiers\n\nIdentifier: `token_position_identifiers`\n\nCheck that each token has a position specifier with it. e.g. `%s` is not allowed, but `%1$s` is. Tokens can move around in different languages, so position specifiers are extremely important.\n\n<details>\n <summary>Configuration</summary>\n\n| Parameter | Type | Acceptable Values | Default | Details | \n| --- | --- | --- | --- | --- |\n| `always` | boolean | `true` or `false` | `false` | If a string only has a single token, it doesn\'t need a position specifier. Set this to `true` to require it even in those cases.\n\n</details>\n\n# Contributing\n\nThis project welcomes contributions and suggestions. Most contributions require you to agree to a\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\nthe rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.\n\nWhen you submit a pull request, a CLA bot will automatically determine whether you need to provide\na CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\nprovided by the bot. You will only need to do this once across all repos using our CLA.\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\ncontact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n',
18
- 'author': 'Dale Myers',
19
- 'author_email': 'dalemy@microsoft.com',
20
- 'maintainer': None,
21
- 'maintainer_email': None,
22
- 'url': 'https://github.com/Microsoft/localizationkit',
23
- 'packages': packages,
24
- 'package_data': package_data,
25
- 'install_requires': install_requires,
26
- 'python_requires': '>=3.7.2,<4.0.0',
27
- }
28
-
29
-
30
- setup(**setup_kwargs)
File without changes