localizationkit 3.1.0__tar.gz → 4.0.0__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 (56) hide show
  1. localizationkit-4.0.0/.azure/publish.yml +43 -0
  2. localizationkit-4.0.0/.github/dependabot.yml +19 -0
  3. localizationkit-4.0.0/.github/workflows/test.yml +78 -0
  4. localizationkit-4.0.0/.gitignore +13 -0
  5. localizationkit-4.0.0/.pre-commit-config.yaml +26 -0
  6. localizationkit-4.0.0/.python-version +1 -0
  7. localizationkit-4.0.0/.vscode/settings.json +15 -0
  8. localizationkit-4.0.0/CODE_OF_CONDUCT.md +9 -0
  9. {localizationkit-3.1.0 → localizationkit-4.0.0}/PKG-INFO +10 -15
  10. localizationkit-4.0.0/SECURITY.md +35 -0
  11. localizationkit-4.0.0/SUPPORT.md +12 -0
  12. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/__init__.py +15 -13
  13. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/configuration.py +4 -5
  14. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/localization_types.py +4 -7
  15. localizationkit-4.0.0/localizationkit/py.typed +0 -0
  16. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/comment_linebreaks.py +0 -1
  17. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/duplicate_keys.py +1 -6
  18. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/has_comments.py +1 -5
  19. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/has_value.py +1 -3
  20. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/invalid_tokens.py +9 -2
  21. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/key_length.py +0 -1
  22. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/objectivec_alternative_tokens.py +0 -1
  23. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/swift_interpolation.py +0 -1
  24. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/test_case.py +1 -3
  25. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/token_adjacency.py +12 -14
  26. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/token_matching.py +1 -3
  27. localizationkit-4.0.0/pyproject.toml +75 -0
  28. localizationkit-4.0.0/stylecheck.sh +7 -0
  29. localizationkit-4.0.0/test.sh +5 -0
  30. localizationkit-4.0.0/tests/__init__.py +0 -0
  31. localizationkit-4.0.0/tests/config.toml +18 -0
  32. localizationkit-4.0.0/tests/conftest.py +18 -0
  33. localizationkit-4.0.0/tests/test_base_calls.py +15 -0
  34. localizationkit-4.0.0/tests/test_comment_linebreaks.py +44 -0
  35. localizationkit-4.0.0/tests/test_comment_similarity.py +16 -0
  36. localizationkit-4.0.0/tests/test_duplicate_keys.py +44 -0
  37. localizationkit-4.0.0/tests/test_has_comments.py +32 -0
  38. localizationkit-4.0.0/tests/test_has_value.py +29 -0
  39. localizationkit-4.0.0/tests/test_invalid_tokens.py +42 -0
  40. localizationkit-4.0.0/tests/test_key_length.py +35 -0
  41. localizationkit-4.0.0/tests/test_objectivec_alternative_tokens.py +33 -0
  42. localizationkit-4.0.0/tests/test_placeholder_token_explanation.py +65 -0
  43. localizationkit-4.0.0/tests/test_position_identifiers.py +49 -0
  44. localizationkit-4.0.0/tests/test_swift_interpolation.py +38 -0
  45. localizationkit-4.0.0/tests/test_token_adjacency.py +48 -0
  46. localizationkit-4.0.0/tests/test_token_matching.py +70 -0
  47. localizationkit-4.0.0/uv.lock +621 -0
  48. localizationkit-3.1.0/pyproject.toml +0 -45
  49. {localizationkit-3.1.0 → localizationkit-4.0.0}/LICENSE +0 -0
  50. {localizationkit-3.1.0 → localizationkit-4.0.0}/README.md +0 -0
  51. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/exceptions.py +0 -0
  52. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/__init__.py +0 -0
  53. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/comment_similarity.py +0 -0
  54. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/placeholder_token_explanation.py +0 -0
  55. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/tests/token_position_identifiers.py +0 -0
  56. {localizationkit-3.1.0 → localizationkit-4.0.0}/localizationkit/utility_types.py +0 -0
@@ -0,0 +1,43 @@
1
+ trigger:
2
+ tags:
3
+ include:
4
+ - "*"
5
+
6
+ pool:
7
+ vmImage: "ubuntu-latest"
8
+
9
+ jobs:
10
+ - job: "Publish"
11
+ pool:
12
+ vmImage: "ubuntu-latest"
13
+
14
+ steps:
15
+ - task: UsePythonVersion@0
16
+ inputs:
17
+ versionSpec: "3.x"
18
+
19
+ - script: |
20
+ curl -LsSf https://astral.sh/uv/install.sh | sh
21
+ echo "##vso[task.prependpath]$HOME/.local/bin"
22
+ displayName: "Install uv"
23
+
24
+ - script: |
25
+ # hatch-vcs derives the version from git tags, so full history is required.
26
+ git fetch --tags --unshallow || git fetch --tags
27
+ displayName: "Fetch tags"
28
+
29
+ - script: |
30
+ uv build
31
+ displayName: "Build"
32
+
33
+ - task: EsrpRelease@2
34
+ inputs:
35
+ ConnectedServiceName: "OM-ESRP-Release-Publishing"
36
+ Intent: "PackageDistribution"
37
+ ContentType: "PyPi"
38
+ PackageLocation: "dist"
39
+ Owners: "jizhen@microsoft.com"
40
+ Approvers: "jizhen@microsoft.com"
41
+ ServiceEndpointUrl: "https://api.esrp.microsoft.com"
42
+ MainPublisher: "OutlookMobileiOSESRPPublisher"
43
+ DomainTenantId: "72f988bf-86f1-41af-91ab-2d7cd011db47"
@@ -0,0 +1,19 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ python-dependencies:
9
+ patterns:
10
+ - "*"
11
+
12
+ - package-ecosystem: "github-actions"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ groups:
17
+ github-actions:
18
+ patterns:
19
+ - "*"
@@ -0,0 +1,78 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+
16
+ jobs:
17
+ test:
18
+ name: Test (Python ${{ matrix.python-version }})
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version:
24
+ - "3.11"
25
+ - "3.12"
26
+ - "3.13"
27
+ - "3.14"
28
+
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v8.2.0
34
+ with:
35
+ enable-cache: true
36
+ python-version: ${{ matrix.python-version }}
37
+
38
+ - name: Install dependencies
39
+ run: uv sync
40
+
41
+ - name: Lint
42
+ run: |
43
+ uv run ruff check localizationkit tests
44
+ uv run ruff format --check localizationkit tests
45
+
46
+ - name: Type Check
47
+ run: uv run mypy localizationkit/ tests/
48
+
49
+ - name: Test
50
+ run: uv run pytest tests --cov=localizationkit --cov-report xml --doctest-modules --junitxml=junit/test-results.xml
51
+
52
+ - name: Upload coverage report
53
+ if: always()
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ name: coverage-${{ matrix.python-version }}
57
+ path: coverage.xml
58
+
59
+ - name: Upload test results
60
+ if: always()
61
+ uses: actions/upload-artifact@v4
62
+ with:
63
+ name: test-results-${{ matrix.python-version }}
64
+ path: junit/test-results.xml
65
+
66
+ test-result:
67
+ name: test-result
68
+ if: always()
69
+ needs: test
70
+ runs-on: ubuntu-latest
71
+ steps:
72
+ - name: Verify all matrix jobs passed
73
+ run: |
74
+ if [ "${{ needs.test.result }}" != "success" ]; then
75
+ echo "One or more test matrix jobs failed."
76
+ exit 1
77
+ fi
78
+ echo "All test matrix jobs passed."
@@ -0,0 +1,13 @@
1
+ htmlcov
2
+ junit
3
+ __pycache__
4
+ venv
5
+ .coverage
6
+ coverage.xml
7
+ dist
8
+ .env
9
+ .venv
10
+ .mypy_cache
11
+ .pytest_cache
12
+ .ruff_cache
13
+ *.egg-info
@@ -0,0 +1,26 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: check-added-large-files
11
+
12
+ - repo: https://github.com/astral-sh/ruff-pre-commit
13
+ rev: v0.15.20
14
+ hooks:
15
+ - id: ruff-check
16
+ args: [--fix]
17
+ - id: ruff-format
18
+
19
+ - repo: local
20
+ hooks:
21
+ - id: mypy
22
+ name: mypy
23
+ entry: uv run mypy localizationkit/ tests/
24
+ language: system
25
+ types: [python]
26
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,15 @@
1
+ {
2
+ "python.testing.pytestArgs": [
3
+ "tests"
4
+ ],
5
+ "python.testing.unittestEnabled": false,
6
+ "python.testing.pytestEnabled": true,
7
+ "editor.formatOnSave": true,
8
+ "[python]": {
9
+ "editor.defaultFormatter": "charliermarsh.ruff",
10
+ "editor.codeActionsOnSave": {
11
+ "source.fixAll.ruff": "explicit",
12
+ "source.organizeImports.ruff": "explicit"
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,9 @@
1
+ # Microsoft Open Source Code of Conduct
2
+
3
+ This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4
+
5
+ Resources:
6
+
7
+ - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8
+ - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9
+ - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
@@ -1,28 +1,24 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: localizationkit
3
- Version: 3.1.0
3
+ Version: 4.0.0
4
4
  Summary: String localization tests
5
- Home-page: https://github.com/Microsoft/localizationkit
6
- License: MIT
5
+ Project-URL: homepage, https://github.com/Microsoft/localizationkit
6
+ Project-URL: repository, https://github.com/Microsoft/localizationkit
7
+ Author-email: Dale Myers <dalemy@microsoft.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
7
10
  Keywords: localization,strings,tests
8
- Author: Dale Myers
9
- Author-email: dalemy@microsoft.com
10
- Requires-Python: >=3.11,<4.0
11
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Programming Language :: Python :: 3
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.8
20
- Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.14
21
18
  Classifier: Topic :: Software Development
22
19
  Classifier: Topic :: Software Development :: Testing
23
20
  Classifier: Topic :: Utilities
24
- Requires-Dist: toml (>=0.10.2,<0.11.0)
25
- Project-URL: Repository, https://github.com/Microsoft/localizationkit
21
+ Requires-Python: <4.0,>=3.11
26
22
  Description-Content-Type: text/markdown
27
23
 
28
24
  # localizationkit
@@ -299,4 +295,3 @@ provided by the bot. You will only need to do this once across all repos using o
299
295
  This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
300
296
  For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
301
297
  contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
302
-
@@ -0,0 +1,35 @@
1
+ <!-- BEGIN MICROSOFT SECURITY.MD V0.0.1 BLOCK -->
2
+
3
+ ## Security
4
+
5
+ Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/).
6
+
7
+ If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
8
+
9
+ ## Reporting Security Issues
10
+
11
+ **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center at [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://technet.microsoft.com/en-us/security/dn606155).
12
+
13
+ You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
14
+
15
+ Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
16
+
17
+ * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
18
+ * Full paths of source file(s) related to the manifestation of the issue
19
+ * The location of the affected source code (tag/branch/commit or direct URL)
20
+ * Any special configuration required to reproduce the issue
21
+ * Step-by-step instructions to reproduce the issue
22
+ * Proof-of-concept or exploit code (if possible)
23
+ * Impact of the issue, including how an attacker might exploit the issue
24
+
25
+ This information will help us triage your report more quickly.
26
+
27
+ ## Preferred Languages
28
+
29
+ We prefer all communications to be in English.
30
+
31
+ ## Policy
32
+
33
+ Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
34
+
35
+ <!-- END MICROSOFT SECURITY.MD BLOCK -->
@@ -0,0 +1,12 @@
1
+ # Support
2
+
3
+ ## How to file issues
4
+
5
+ This project uses GitHub Issues to track bugs and feature requests. Please search the existing
6
+ issues before filing new issues to avoid duplicates. For new issues, file your bug or
7
+ feature request as a new Issue.
8
+
9
+
10
+ ## Microsoft Support Policy
11
+
12
+ Support for this tool is limited to the resources listed above.
@@ -1,32 +1,36 @@
1
1
  """Toolkit for validation of localized strings."""
2
2
 
3
3
  import inspect
4
- from typing import Type
5
4
 
5
+ from localizationkit import tests
6
6
  from localizationkit.configuration import Configuration
7
7
  from localizationkit.exceptions import LocalizationKitException
8
8
  from localizationkit.localization_types import LocalizedCollection, LocalizedString
9
- from localizationkit.utility_types import TestResult
10
9
  from localizationkit.tests.test_case import LocalizationTestCase
10
+ from localizationkit.utility_types import TestResult
11
11
 
12
- from localizationkit import tests
12
+ __all__ = [
13
+ "Configuration",
14
+ "LocalizationKitException",
15
+ "LocalizationTestCase",
16
+ "LocalizedCollection",
17
+ "LocalizedString",
18
+ "TestResult",
19
+ "run_tests",
20
+ ]
13
21
 
14
22
 
15
- def _find_tests() -> list[Type[LocalizationTestCase]]:
23
+ def _find_tests() -> list[type[LocalizationTestCase]]:
16
24
  """Find all the tests."""
17
25
 
18
- test_module_names = [
19
- module_name for module_name in dir(tests) if not module_name.startswith("__")
20
- ]
26
+ test_module_names = [module_name for module_name in dir(tests) if not module_name.startswith("__")]
21
27
  test_modules = [getattr(tests, name) for name in test_module_names]
22
28
 
23
29
  names_seen: set[str] = set()
24
- test_classes: list[Type[LocalizationTestCase]] = []
30
+ test_classes: list[type[LocalizationTestCase]] = []
25
31
 
26
32
  for module in test_modules:
27
-
28
33
  for reference_name in dir(module):
29
-
30
34
  reference = getattr(module, reference_name)
31
35
 
32
36
  if not inspect.isclass(reference):
@@ -39,9 +43,7 @@ def _find_tests() -> list[Type[LocalizationTestCase]]:
39
43
  continue
40
44
 
41
45
  if reference.__name__ in names_seen:
42
- raise LocalizationKitException(
43
- "At least 2 classes exist with the same name: " + reference.__name__
44
- )
46
+ raise LocalizationKitException("At least 2 classes exist with the same name: " + reference.__name__)
45
47
 
46
48
  test_classes.append(reference)
47
49
 
@@ -1,8 +1,7 @@
1
1
  """Configuration for localization tests."""
2
2
 
3
- from typing import Any, cast
4
-
5
- import toml
3
+ import tomllib
4
+ from typing import Any
6
5
 
7
6
 
8
7
  class Configuration:
@@ -54,5 +53,5 @@ class Configuration:
54
53
  :returns: The parsed configuration object
55
54
  """
56
55
 
57
- with open(file_path, encoding="utf-8") as config_file:
58
- return Configuration(cast(dict[str, Any], toml.load(config_file)))
56
+ with open(file_path, "rb") as config_file:
57
+ return Configuration(tomllib.load(config_file))
@@ -1,7 +1,8 @@
1
1
  """Types for localization."""
2
2
 
3
3
  import re
4
- from typing import ClassVar, Pattern
4
+ from re import Pattern
5
+ from typing import ClassVar
5
6
 
6
7
 
7
8
  class LocalizedString:
@@ -13,9 +14,7 @@ class LocalizedString:
13
14
  :param language_code: The language code for the language the string is in
14
15
  """
15
16
 
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
- )
17
+ _TOKEN_REGEX: ClassVar[str] = r"(%(?:[0-9]+\$)?[0-9]*\.?[0-9]*(?:h|hh|l|ll|q|L|z|t|j){0,2}[dDuUxXoOfFeEgGcCsSaAp@])"
19
18
  _TOKEN_PATTERN: ClassVar[Pattern] = re.compile(_TOKEN_REGEX, flags=re.DOTALL)
20
19
 
21
20
  key: str
@@ -92,9 +91,7 @@ class LocalizedCollection:
92
91
 
93
92
  :returns: The list of strings with the matching language code
94
93
  """
95
- return [
96
- string for string in self.localized_strings if string.language_code == language_code
97
- ]
94
+ return [string for string in self.localized_strings if string.language_code == language_code]
98
95
 
99
96
  def languages(self) -> list[str]:
100
97
  """Return the list of languages in the collection.
File without changes
@@ -24,7 +24,6 @@ class CommentLinebreaks(LocalizationTestCase):
24
24
  violations = []
25
25
 
26
26
  for string in self.collection.strings_for_language(self.configuration.default_language()):
27
-
28
27
  if string.comment is None:
29
28
  continue
30
29
 
@@ -22,17 +22,12 @@ class DuplicateKeys(LocalizationTestCase):
22
22
 
23
23
  all_languages = self.get_setting("all_languages")
24
24
 
25
- if all_languages:
26
- languages_to_check = self.collection.languages()
27
- else:
28
- languages_to_check = [self.configuration.default_language()]
25
+ languages_to_check = self.collection.languages() if all_languages else [self.configuration.default_language()]
29
26
 
30
27
  for language in languages_to_check:
31
-
32
28
  keys: set[str] = set()
33
29
 
34
30
  for string in self.collection.strings_for_language(language):
35
-
36
31
  if string.key in keys:
37
32
  violations.append(
38
33
  (
@@ -24,7 +24,6 @@ class HasComments(LocalizationTestCase):
24
24
  violations = []
25
25
 
26
26
  for string in self.collection.strings_for_language(self.configuration.default_language()):
27
-
28
27
  if string.comment is None:
29
28
  violations.append((f"Comment was empty: {string}", string.language_code))
30
29
  continue
@@ -38,10 +37,7 @@ class HasComments(LocalizationTestCase):
38
37
  )
39
38
  continue
40
39
 
41
- if (
42
- minimum_comment_words >= 0
43
- and len(string.comment.split(" ")) < minimum_comment_words
44
- ):
40
+ if minimum_comment_words >= 0 and len(string.comment.split(" ")) < minimum_comment_words:
45
41
  violations.append(
46
42
  (
47
43
  f"Comment did not meet minimum word count of {minimum_comment_words}: {string}",
@@ -21,9 +21,7 @@ class HasValue(LocalizationTestCase):
21
21
  violations: list[tuple[str, str]] = []
22
22
 
23
23
  if self.get_setting("default_language_only"):
24
- test_collection = self.collection.strings_for_language(
25
- self.configuration.default_language()
26
- )
24
+ test_collection = self.collection.strings_for_language(self.configuration.default_language())
27
25
  else:
28
26
  test_collection = self.collection.localized_strings
29
27
 
@@ -21,10 +21,17 @@ class InvalidTokens(LocalizationTestCase):
21
21
 
22
22
  violations = []
23
23
 
24
- invalid_token_pattern = re.compile(r"(%[^@%\.a-zA-Z0-9 ]+)", flags=re.DOTALL)
24
+ # "%%" is the printf escape for a literal percent sign and is always
25
+ # valid, so consume it first in the alternation. The capturing group
26
+ # only matches a "%" that starts something which is *not* a valid
27
+ # conversion (i.e. is followed by a character other than a conversion
28
+ # flag/specifier, "@", "%", ".", alphanumeric, or space). Matching "%%"
29
+ # via the first branch leaves an empty capture group, which is filtered
30
+ # out below, so "50%%)" no longer trips on the trailing "%)".
31
+ invalid_token_pattern = re.compile(r"%%|(%[^@%\.a-zA-Z0-9 ]+)", flags=re.DOTALL)
25
32
 
26
33
  for string in self.collection.localized_strings:
27
- matches = invalid_token_pattern.findall(string.value)
34
+ matches = [match for match in invalid_token_pattern.findall(string.value) if match]
28
35
 
29
36
  # Any matches are a bad thing
30
37
  if matches and len(matches) > 0:
@@ -28,7 +28,6 @@ class KeyLength(LocalizationTestCase):
28
28
  return violations
29
29
 
30
30
  for string in self.collection.strings_for_language(self.configuration.default_language()):
31
-
32
31
  key = string.key
33
32
 
34
33
  if key is None:
@@ -33,7 +33,6 @@ class ObjectivecAlternativeTokens(LocalizationTestCase):
33
33
  pattern = re.compile(r"(%(?:[0-9]+)(?:\.[0-9]+)?[@a-z][a-z]?)", flags=re.DOTALL)
34
34
 
35
35
  for string in self.collection.localized_strings:
36
-
37
36
  if pattern.findall(string.value):
38
37
  violations.append(
39
38
  (
@@ -28,7 +28,6 @@ class SwiftInterpolation(LocalizationTestCase):
28
28
  pattern = re.compile(r'\\\([^\)"]*\)', flags=re.DOTALL)
29
29
 
30
30
  for string in self.collection.localized_strings:
31
-
32
31
  if pattern.findall(string.value):
33
32
  violations.append(
34
33
  (
@@ -81,9 +81,7 @@ class LocalizationTestCase(abc.ABC):
81
81
  violations = self.run_test()
82
82
 
83
83
  if violations is None:
84
- raise LocalizationKitException(
85
- f"Failed to get exceptions from test: {self.__class__.name()}"
86
- )
84
+ raise LocalizationKitException(f"Failed to get exceptions from test: {self.__class__.name()}")
87
85
 
88
86
  if len(violations):
89
87
  return TestResult.failure(self.__class__.name(), violations)
@@ -40,23 +40,21 @@ class TokenAdjacency(LocalizationTestCase):
40
40
 
41
41
  for token, start, end in tokens:
42
42
  preceding_char = string.value[start - 1] if start > 0 else None
43
- if preceding_char:
44
- if preceding_char not in allowed_characters:
45
- violations.append(
46
- (
47
- f"Token '{token}' is directly preceded by '{preceding_char}' in string: {string}",
48
- string.language_code,
49
- )
43
+ if preceding_char and preceding_char not in allowed_characters:
44
+ violations.append(
45
+ (
46
+ f"Token '{token}' is directly preceded by '{preceding_char}' in string: {string}",
47
+ string.language_code,
50
48
  )
49
+ )
51
50
 
52
51
  succeeding_char = string.value[end] if end < len(string.value) else None
53
- if succeeding_char:
54
- if succeeding_char not in allowed_characters:
55
- violations.append(
56
- (
57
- f"Token '{token}' is directly followed by '{succeeding_char}' in string: {string}",
58
- string.language_code,
59
- )
52
+ if succeeding_char and succeeding_char not in allowed_characters:
53
+ violations.append(
54
+ (
55
+ f"Token '{token}' is directly followed by '{succeeding_char}' in string: {string}",
56
+ string.language_code,
60
57
  )
58
+ )
61
59
 
62
60
  return violations
@@ -28,13 +28,11 @@ class TokenMatching(LocalizationTestCase):
28
28
  default_token_map[string.key] = sorted(string.tokens())
29
29
 
30
30
  for language_code in self.collection.languages():
31
-
32
31
  if language_code == self.configuration.default_language():
33
32
  continue
34
33
 
35
34
  for string in self.collection.strings_for_language(language_code):
36
-
37
- default_tokens = default_token_map.get(string.key, None)
35
+ default_tokens = default_token_map.get(string.key)
38
36
 
39
37
  # Sometimes a string can be removed from the default language
40
38
  # before it is removed from other languages (due to automation)