-
Notifications
You must be signed in to change notification settings - Fork 4
Add scripts to allow addons from personal repos to be synchronized with Crowdin #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
28c060d
4669430
b188560
7092615
e89640d
4c7771b
c529cee
186b755
f1fbf8e
b867a9a
47ed91c
402002e
d820711
9f6b3dc
4c938ec
a303210
a0d02da
a8d4252
1a1e6fd
d2395b0
e4dafe1
f76904e
aea5eba
f7ccaf6
0276e22
253eb46
c51e7ad
cd4816c
314220b
f3e8b8d
de4fa15
46a105a
053d4de
4a3f5a0
3c1a73e
dbe74dc
e717292
c4ed575
befa647
05c8161
9a0f62a
4abd788
c256364
0505a3b
fd2554b
b30f46f
70293c8
da09c8c
5c52f33
d0d5e03
b40f94a
cb7807e
1449a01
697d048
8c9247b
a8469fb
6089057
d8154a9
e699abc
5eafa64
0eb3223
a195110
9ed44a3
e62bd5b
37c4d4f
91995bd
0c613be
0b65072
f9ba8fe
8e514ba
9c64247
7589fef
94b1a88
65290e4
059bb53
c9c3bab
25dcb5d
b1d2acf
263b7e2
f6e18c2
5573061
87cbed4
1a18717
471a320
e50a716
f5150cb
3c7093c
065a16f
c4dd521
00cf8cf
480c70e
ce2d2da
3981ece
0def05d
1c92aab
9b5bf84
c5e4076
c098392
39416a0
2396864
94929e7
57d46f5
74388d0
1f8eb84
ef0a614
92e1cd6
f92a73f
6a884c0
3100c81
6fe4efc
a3209ac
6c09158
50b41ab
4646c5e
56ff1e0
79bdff2
76ed71e
e423d6f
2d477f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| # checkTranslation.py | ||
| # Copyright (C) 2026 NV Access Limited, Abdel | ||
| # This file is covered by the GNU General Public License. | ||
| # See the file COPYING for more details. | ||
|
|
||
| import sys | ||
| import os | ||
| from crowdin_api import CrowdinClient | ||
|
|
||
|
|
||
| def findFileId(client: CrowdinClient, projectId: int, baseTarget: str, searchExt: str) -> int | None: | ||
| """ | ||
| Iterates through all project files (using pagination) to find the ID of the source file matching the target name and extension. | ||
|
|
||
| :param client: The Crowdin API client instance. | ||
| :param projectId: The ID of the Crowdin project. | ||
| :param base_target: The base name of the file (e.g., 'myAddon). | ||
| :param search_ext: The extension to look for (e.g., '.pot'). | ||
| :return: The file ID if found, otherwise None. | ||
| """ | ||
| offset = 0 | ||
| limit = 100 | ||
|
|
||
| while True: | ||
| resp = client.source_files.list_files( | ||
| projectId=projectId, | ||
| limit=limit, | ||
| offset=offset, | ||
| ) | ||
|
|
||
| data = resp["data"] | ||
| for f in data: | ||
| path_crowdin = f["data"]["path"].lower() | ||
| # Check if the path ends with addon_id.pot or addon_id.xliff. | ||
| if path_crowdin.endswith(f"{baseTarget}{searchExt}"): | ||
| fileId = f["data"]["id"] | ||
| print(f"DEBUG: Match found: {path_crowdin} (ID: {fileId})") | ||
| return fileId | ||
|
|
||
| if len(data) < limit: | ||
| break | ||
|
|
||
| offset += limit | ||
|
|
||
| return None | ||
|
|
||
|
|
||
| def getScoreFromApi(fileNameToSearch: str, langId: str) -> float: | ||
| """ | ||
| Retrieves the translation progress score for a specific language and file. | ||
| Handles pagination for both file listing and language status. | ||
|
|
||
| :param fileNameToSearch: The local path or name of the file to check. | ||
| :param langId: The language code (e.g., 'fr' or 'pt_BR'). | ||
| :return: The translation ratio between 0.0 and 1.0. | ||
| """ | ||
| token = os.environ.get("crowdinAuthToken") | ||
| projectIdEnv = os.environ.get("CROWDIN_PROJECT_ID") | ||
|
|
||
| if not token or not projectIdEnv: | ||
| print("ERROR: Missing environment variables 'crowdinAuthToken' or 'CROWDIN_PROJECT_ID'.") | ||
| return 0.0 | ||
|
|
||
| client = CrowdinClient(token=token) | ||
| projectId = int(projectIdEnv) | ||
|
|
||
| try: | ||
| # Clean and prepare search patterns. | ||
| # Example: 'addon/locale/fr/LC_MESSAGES/myAddon.po' -> base_target: 'myAddon'. | ||
| baseTarget = fileNameToSearch.replace("\\", "/").split("/")[-1].rsplit(".", 1)[0].lower() | ||
| extTarget = fileNameToSearch.split(".")[-1].lower() | ||
|
|
||
| # On Crowdin, the source for a .po file is usually a .pot file. | ||
| searchExt = ".pot" if extTarget == "po" else f".{extTarget}" | ||
|
|
||
| print(f"DEBUG: Searching for source file: {baseTarget}{searchExt}") | ||
|
|
||
| fileId = findFileId(client, projectId, baseTarget, searchExt) | ||
|
|
||
| if fileId is None: | ||
| print(f"WARNING: File '{baseTarget}{searchExt}' not found on Crowdin.") | ||
| return 0.0 | ||
|
|
||
| # Pagination for translation status (Progress). | ||
| offset = 0 | ||
| limit = 100 | ||
|
|
||
| while True: | ||
| resp = client.translation_status.get_file_progress( | ||
| projectId=projectId, | ||
| fileId=fileId, | ||
| limit=limit, | ||
| offset=offset, | ||
| ) | ||
|
|
||
| data = resp["data"] | ||
| for item in data: | ||
| langApi = item["data"]["languageId"] | ||
|
|
||
| # Flexible matching (e.g., 'fr' will match 'fr' or 'fr-FR' from API). | ||
| # Also handles underscore to dash conversion for Crowdin compatibility | ||
| if langApi.lower().startswith(langId.lower().replace("_", "-")): | ||
| progress = float(item["data"]["translationProgress"]) | ||
| return progress / 100 | ||
|
|
||
| # Check pagination total. | ||
| total = resp["pagination"]["totalCount"] | ||
| if offset + limit >= total: | ||
| break | ||
| offset += limit | ||
|
|
||
| print(f"DEBUG: Language '{langId}' not found in progress list for this file.") | ||
| return 0.0 | ||
|
|
||
| except Exception as e: | ||
| print(f"API ERROR: {e}") | ||
| return 0.0 | ||
|
|
||
|
|
||
| def main(): | ||
| if len(sys.argv) < 3: | ||
| print("Usage: python checkTranslation.py <filePath> <langId>") | ||
| sys.exit(2) | ||
|
|
||
| input_file = sys.argv[1] | ||
| lang = sys.argv[2] | ||
|
|
||
| score = getScoreFromApi(input_file, lang) | ||
|
|
||
| # Output formatted for capture by the PowerShell script. | ||
| print(f"translationRatio={score}") | ||
|
|
||
| # Identify extension to provide a specific score label. | ||
| ext = input_file.lower().split(".")[-1] | ||
| if ext == "md": | ||
| print(f"mdScore={score}") | ||
| elif ext == "xliff": | ||
| print(f"xliffScore={score}") | ||
| else: | ||
| # Default to poScore for .po and other localization files. | ||
| print(f"poScore={score}") | ||
|
|
||
| # Exit with success (0) if there is at least 50% translated content. | ||
| sys.exit(0 if score > 0.5 else 1) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,173 @@ | ||||||||||||||||||||||||||
| #!/usr/bin/env pwsh | ||||||||||||||||||||||||||
| $ErrorActionPreference = 'Stop' | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Git configuration for automated commits | ||||||||||||||||||||||||||
| git config user.name "github-actions[bot]" | ||||||||||||||||||||||||||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| $addonId = $env:ADDON_ID.Trim() | ||||||||||||||||||||||||||
| if (-not $addonId) { | ||||||||||||||||||||||||||
| Write-Error "Failed to get addon ID." | ||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # --- STEP 1: PREPARATION AND SOURCE UPDATE --- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| $xliffFile = "./$addonId.xliff" | ||||||||||||||||||||||||||
| $mdFile = "./readme.md" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (Test-Path $mdFile) { | ||||||||||||||||||||||||||
| if (Test-Path $xliffFile) { | ||||||||||||||||||||||||||
| $tempXliff = [System.IO.Path]::GetTempFileName() | ||||||||||||||||||||||||||
| Copy-Item "$addonId.xliff" $tempXliff -Force | ||||||||||||||||||||||||||
| Write-Host "DEBUG: Updating XLIFF source based on readme.md..." | ||||||||||||||||||||||||||
| uv run .github/scripts/markdownTranslate.py updateXliff -m $mdFile -x $tempXliff -o $xliffFile | ||||||||||||||||||||||||||
|
Comment on lines
+22
to
+24
|
||||||||||||||||||||||||||
| Copy-Item "$addonId.xliff" $tempXliff -Force | |
| Write-Host "DEBUG: Updating XLIFF source based on readme.md..." | |
| uv run .github/scripts/markdownTranslate.py updateXliff -m $mdFile -x $tempXliff -o $xliffFile | |
| try { | |
| Copy-Item "$addonId.xliff" $tempXliff -Force | |
| Write-Host "DEBUG: Updating XLIFF source based on readme.md..." | |
| uv run .github/scripts/markdownTranslate.py updateXliff -m $mdFile -x $tempXliff -o $xliffFile | |
| } finally { | |
| if (Test-Path $tempXliff) { | |
| Remove-Item $tempXliff -Force | |
| } | |
| } |
Copilot
AI
Apr 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uv run .github/scripts/markdownTranslate.py ... attempts to execute the Python file as a program. Since markdownTranslate.py isn’t an executable with a shebang, this is likely to fail under uv run. Call it explicitly via Python (e.g., uv run python .github/scripts/markdownTranslate.py ...) for both the update and generate commands.
| uv run .github/scripts/markdownTranslate.py updateXliff -m $mdFile -x $tempXliff -o $xliffFile | |
| } else { | |
| Write-Host "DEBUG: XLIFF template not found. Creating new one from readme.md..." | |
| uv run .github/scripts/markdownTranslate.py generateXliff -m $mdFile -o $xliffFile | |
| uv run python .github/scripts/markdownTranslate.py updateXliff -m $mdFile -x $tempXliff -o $xliffFile | |
| } else { | |
| Write-Host "DEBUG: XLIFF template not found. Creating new one from readme.md..." | |
| uv run python .github/scripts/markdownTranslate.py generateXliff -m $mdFile -o $xliffFile |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "af_ZA": "af", | ||
| "de_CH": "de-CH", | ||
| "es": "es-ES", | ||
| "es_CO": "es-CO", | ||
| "nb_NO": "nb", | ||
| "nn_NO": "nn-NO", | ||
| "pt_PT": "pt-PT", | ||
| "pt_BR": "pt-BR", | ||
| "sr": "sr-CS", | ||
| "zh_CN": "zh-CN", | ||
| "zh_HK": "zh-HK", | ||
| "zh_TW": "zh-TW" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$env:ADDON_ID.Trim()will throw ifADDON_IDisn’t set (null), so the script can terminate before reaching the intended error message. Read the env var first and only call.Trim()after verifying it’s non-null/non-empty (or use[string]::IsNullOrWhiteSpace).