localizationkit 3.0.1__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.
- localizationkit-4.0.0/.azure/publish.yml +43 -0
- localizationkit-4.0.0/.github/dependabot.yml +19 -0
- localizationkit-4.0.0/.github/workflows/test.yml +78 -0
- localizationkit-4.0.0/.gitignore +13 -0
- localizationkit-4.0.0/.pre-commit-config.yaml +26 -0
- localizationkit-4.0.0/.python-version +1 -0
- localizationkit-4.0.0/.vscode/settings.json +15 -0
- localizationkit-4.0.0/CODE_OF_CONDUCT.md +9 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/PKG-INFO +28 -15
- {localizationkit-3.0.1 → localizationkit-4.0.0}/README.md +18 -0
- localizationkit-4.0.0/SECURITY.md +35 -0
- localizationkit-4.0.0/SUPPORT.md +12 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/__init__.py +15 -13
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/configuration.py +4 -5
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/localization_types.py +12 -7
- localizationkit-4.0.0/localizationkit/py.typed +0 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/comment_linebreaks.py +0 -1
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/duplicate_keys.py +1 -6
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/has_comments.py +1 -5
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/has_value.py +1 -3
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/invalid_tokens.py +9 -2
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/key_length.py +0 -1
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/objectivec_alternative_tokens.py +0 -1
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/swift_interpolation.py +0 -1
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/test_case.py +1 -3
- localizationkit-4.0.0/localizationkit/tests/token_adjacency.py +60 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/token_matching.py +1 -3
- localizationkit-4.0.0/pyproject.toml +75 -0
- localizationkit-4.0.0/stylecheck.sh +7 -0
- localizationkit-4.0.0/test.sh +5 -0
- localizationkit-4.0.0/tests/__init__.py +0 -0
- localizationkit-4.0.0/tests/config.toml +18 -0
- localizationkit-4.0.0/tests/conftest.py +18 -0
- localizationkit-4.0.0/tests/test_base_calls.py +15 -0
- localizationkit-4.0.0/tests/test_comment_linebreaks.py +44 -0
- localizationkit-4.0.0/tests/test_comment_similarity.py +16 -0
- localizationkit-4.0.0/tests/test_duplicate_keys.py +44 -0
- localizationkit-4.0.0/tests/test_has_comments.py +32 -0
- localizationkit-4.0.0/tests/test_has_value.py +29 -0
- localizationkit-4.0.0/tests/test_invalid_tokens.py +42 -0
- localizationkit-4.0.0/tests/test_key_length.py +35 -0
- localizationkit-4.0.0/tests/test_objectivec_alternative_tokens.py +33 -0
- localizationkit-4.0.0/tests/test_placeholder_token_explanation.py +65 -0
- localizationkit-4.0.0/tests/test_position_identifiers.py +49 -0
- localizationkit-4.0.0/tests/test_swift_interpolation.py +38 -0
- localizationkit-4.0.0/tests/test_token_adjacency.py +48 -0
- localizationkit-4.0.0/tests/test_token_matching.py +70 -0
- localizationkit-4.0.0/uv.lock +621 -0
- localizationkit-3.0.1/pyproject.toml +0 -47
- {localizationkit-3.0.1 → localizationkit-4.0.0}/LICENSE +0 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/exceptions.py +0 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/__init__.py +0 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/comment_similarity.py +0 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/placeholder_token_explanation.py +0 -0
- {localizationkit-3.0.1 → localizationkit-4.0.0}/localizationkit/tests/token_position_identifiers.py +0 -0
- {localizationkit-3.0.1 → 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,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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: localizationkit
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: String localization tests
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|
|
@@ -238,6 +234,24 @@ Opt-In: `true`
|
|
|
238
234
|
|
|
239
235
|
Checks that strings do not contain Swift style interpolation values since these cannot be localized.
|
|
240
236
|
|
|
237
|
+
## Token Adjacency
|
|
238
|
+
|
|
239
|
+
Identifier: `token_adjacency`
|
|
240
|
+
Opt-In: `true`
|
|
241
|
+
|
|
242
|
+
Checks that attempts for pluralization or other similar operations are not in place, e.g. `Open %@s in the app` doesn't translate well.
|
|
243
|
+
|
|
244
|
+
_Note: It is recommended that this is run on latin alphabet using languages only._
|
|
245
|
+
|
|
246
|
+
<details>
|
|
247
|
+
<summary>Configuration</summary>
|
|
248
|
+
|
|
249
|
+
| Parameter | Type | Acceptable Values | Default | Details |
|
|
250
|
+
| --- | --- | --- | --- | --- |
|
|
251
|
+
| `only_check_language` | str or `None` | Language code | `None` | Set to only check a particular language. **Recommended** |
|
|
252
|
+
|
|
253
|
+
</details>
|
|
254
|
+
|
|
241
255
|
## Token Matching
|
|
242
256
|
|
|
243
257
|
Identifier: `token_matching`
|
|
@@ -281,4 +295,3 @@ provided by the bot. You will only need to do this once across all repos using o
|
|
|
281
295
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
282
296
|
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
283
297
|
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
284
|
-
|
|
@@ -211,6 +211,24 @@ Opt-In: `true`
|
|
|
211
211
|
|
|
212
212
|
Checks that strings do not contain Swift style interpolation values since these cannot be localized.
|
|
213
213
|
|
|
214
|
+
## Token Adjacency
|
|
215
|
+
|
|
216
|
+
Identifier: `token_adjacency`
|
|
217
|
+
Opt-In: `true`
|
|
218
|
+
|
|
219
|
+
Checks that attempts for pluralization or other similar operations are not in place, e.g. `Open %@s in the app` doesn't translate well.
|
|
220
|
+
|
|
221
|
+
_Note: It is recommended that this is run on latin alphabet using languages only._
|
|
222
|
+
|
|
223
|
+
<details>
|
|
224
|
+
<summary>Configuration</summary>
|
|
225
|
+
|
|
226
|
+
| Parameter | Type | Acceptable Values | Default | Details |
|
|
227
|
+
| --- | --- | --- | --- | --- |
|
|
228
|
+
| `only_check_language` | str or `None` | Language code | `None` | Set to only check a particular language. **Recommended** |
|
|
229
|
+
|
|
230
|
+
</details>
|
|
231
|
+
|
|
214
232
|
## Token Matching
|
|
215
233
|
|
|
216
234
|
Identifier: `token_matching`
|
|
@@ -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
|
-
|
|
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[
|
|
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[
|
|
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
|
-
|
|
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,
|
|
58
|
-
return Configuration(
|
|
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
|
|
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
|
|
@@ -36,6 +35,14 @@ class LocalizedString:
|
|
|
36
35
|
"""
|
|
37
36
|
return LocalizedString._TOKEN_PATTERN.findall(self.value)
|
|
38
37
|
|
|
38
|
+
def tokens_with_positions(self) -> list[tuple[str, int, int]]:
|
|
39
|
+
"""Find and return the tokens in the string with their start and end position.
|
|
40
|
+
|
|
41
|
+
:returns: The list of tuples containing the token, start, and end for each.
|
|
42
|
+
"""
|
|
43
|
+
tokens = LocalizedString._TOKEN_PATTERN.finditer(self.value)
|
|
44
|
+
return [(match.group(0), match.start(), match.end()) for match in tokens]
|
|
45
|
+
|
|
39
46
|
def comment_tokens(self) -> list[str]:
|
|
40
47
|
"""Find and return the tokens in the comment string.
|
|
41
48
|
|
|
@@ -84,9 +91,7 @@ class LocalizedCollection:
|
|
|
84
91
|
|
|
85
92
|
:returns: The list of strings with the matching language code
|
|
86
93
|
"""
|
|
87
|
-
return [
|
|
88
|
-
string for string in self.localized_strings if string.language_code == language_code
|
|
89
|
-
]
|
|
94
|
+
return [string for string in self.localized_strings if string.language_code == language_code]
|
|
90
95
|
|
|
91
96
|
def languages(self) -> list[str]:
|
|
92
97
|
"""Return the list of languages in the collection.
|
|
File without changes
|
|
@@ -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
|
-
|
|
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:
|
|
@@ -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
|
(
|
|
@@ -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)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Token matching."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from localizationkit.tests.test_case import LocalizationTestCase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenAdjacency(LocalizationTestCase):
|
|
9
|
+
"""Check that nothing is directly adjacent to a token. e.g. Don't allow `You have new %@s` for plurals."""
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def name(cls) -> str:
|
|
13
|
+
return "token_adjacency"
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def is_opt_in(cls) -> bool:
|
|
17
|
+
"""Check if the test is opt in or not."""
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def default_settings(cls) -> dict[str, Any]:
|
|
22
|
+
return {"only_check_language": None}
|
|
23
|
+
|
|
24
|
+
def run_test(self) -> list[tuple[str, str]]:
|
|
25
|
+
|
|
26
|
+
violations = []
|
|
27
|
+
|
|
28
|
+
only_check_language = self.get_setting("only_check_language")
|
|
29
|
+
|
|
30
|
+
allowed_characters = '.\n\r\t,:;" '
|
|
31
|
+
|
|
32
|
+
for string in self.collection.localized_strings:
|
|
33
|
+
if only_check_language and string.language_code != only_check_language:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
tokens = string.tokens_with_positions()
|
|
37
|
+
|
|
38
|
+
if not tokens or len(tokens) == 0:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
for token, start, end in tokens:
|
|
42
|
+
preceding_char = string.value[start - 1] if start > 0 else None
|
|
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,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
succeeding_char = string.value[end] if end < len(string.value) else None
|
|
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,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return violations
|