-
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 77 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
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,187 @@ | ||
| import sys | ||
| import os | ||
| import xml.etree.ElementTree as ET | ||
| import polib | ||
| import langid | ||
|
|
||
|
|
||
| def normalize(s: str | None) -> str: | ||
| return " ".join((s or "").strip().lower().split()) | ||
|
|
||
|
|
||
| # ----------------------------- | ||
| # PO CHECK | ||
| # ----------------------------- | ||
|
|
||
|
|
||
| def checkPo(path: str) -> float: | ||
| po = polib.pofile(path) | ||
| translated = 0 | ||
| total = 0 | ||
|
|
||
| for entry in po: | ||
| if not entry.msgid.strip(): | ||
| continue | ||
|
|
||
| total += 1 | ||
|
|
||
| if entry.msgstr and normalize(entry.msgstr) != normalize(entry.msgid): | ||
| translated += 1 | ||
|
|
||
| return translated / total if total else 0.0 | ||
|
|
||
|
|
||
| # ----------------------------- | ||
| # XLIFF CHECK | ||
| # ----------------------------- | ||
|
|
||
|
|
||
| def checkXliff(path: str) -> float: | ||
| tree = ET.parse(path) | ||
| root = tree.getroot() | ||
| translated = 0 | ||
| total = 0 | ||
| source = None | ||
|
|
||
| for elem in root.iter(): | ||
| if elem.tag.endswith("source"): | ||
| source = normalize(elem.text) | ||
|
|
||
| elif elem.tag.endswith("target"): | ||
| target = normalize(elem.text) | ||
|
|
||
| if source: | ||
| total += 1 | ||
| if target and target != source: | ||
| translated += 1 | ||
|
|
||
| return translated / total if total else 0.0 | ||
|
|
||
|
|
||
| # ----------------------------- | ||
| # MD LANGUAGE SCORE (langid) | ||
| # ----------------------------- | ||
|
|
||
|
|
||
| def scoreMd(path: str, expected_lang: str) -> float: | ||
| try: | ||
| with open(path, "r", encoding="utf-8") as f: | ||
| text = f.read() | ||
| except Exception: | ||
| return 0.0 | ||
|
|
||
| if not text.strip(): | ||
| return 0.0 | ||
|
|
||
| lang, score = langid.classify(text) | ||
|
|
||
| # Normalize score into positive confidence | ||
| confidence = 1 / (1 + abs(score)) | ||
|
|
||
| if lang == expected_lang: | ||
| return confidence | ||
| else: | ||
| return 0.0 | ||
|
|
||
|
|
||
| # ----------------------------- | ||
| # COMPARE MULTIPLE MD FILES | ||
| # ----------------------------- | ||
|
|
||
|
|
||
| def compareMd(files: list[str], lang: str): | ||
| results = [] | ||
|
|
||
| for f in files: | ||
| if not os.path.exists(f): | ||
| continue | ||
|
|
||
| score = scoreMd(f, lang) | ||
| results.append((f, score)) | ||
|
|
||
| if not results: | ||
| print("winner=None") | ||
| sys.exit(1) | ||
|
|
||
| results.sort(key=lambda x: x[1], reverse=True) | ||
|
|
||
| winner = results[0] | ||
|
|
||
| print("comparison_results:") | ||
| for f, s in results: | ||
| print(f"{f}={s}") | ||
|
|
||
| print(f"winner={winner[0]}") | ||
| print(f"winner_score={winner[1]}") | ||
|
|
||
| sys.exit(0) | ||
|
|
||
|
|
||
| # ----------------------------- | ||
| # MAIN | ||
| # ----------------------------- | ||
|
|
||
|
|
||
| def main(): | ||
| if len(sys.argv) < 2: | ||
| print("Usage:") | ||
| print(" checkTranslation.py <file>") | ||
| print(" checkTranslation.py <file> <lang>") | ||
| print(" checkTranslation.py <file1> <file2> [...] <lang>") | ||
| sys.exit(2) | ||
|
|
||
| args = sys.argv[1:] | ||
|
|
||
| # ------------------------- | ||
| # MULTI FILE MODE | ||
| # ------------------------- | ||
| if len(args) >= 3: | ||
| *files, lang = args | ||
| compareMd(files, lang) | ||
| return | ||
|
|
||
| path = args[0] | ||
|
|
||
| if not os.path.exists(path): | ||
| print(f"File not found: {path}") | ||
| sys.exit(2) | ||
|
|
||
| ext = os.path.splitext(path)[1].lower() | ||
|
|
||
| # ------------------------- | ||
| # PO | ||
| # ------------------------- | ||
| if ext == ".po": | ||
| ratio = checkPo(path) | ||
| print(f"translation_ratio={ratio}") | ||
| sys.exit(0 if ratio > 0.05 else 1) | ||
|
|
||
| # ------------------------- | ||
| # XLIFF | ||
| # ------------------------- | ||
| elif ext in [".xliff", ".xlf"]: | ||
| ratio = checkXliff(path) | ||
| print(f"translation_ratio={ratio}") | ||
| sys.exit(0 if ratio > 0.05 else 1) | ||
|
|
||
| # ------------------------- | ||
| # MD (LANG SCORE) | ||
| # ------------------------- | ||
| elif ext == ".md": | ||
| if len(args) < 2: | ||
| print("Missing language argument for MD scoring") | ||
| sys.exit(2) | ||
|
|
||
| lang = args[1] | ||
| score = scoreMd(path, lang) | ||
|
|
||
| print(f"md_score={score}") | ||
| sys.exit(0) | ||
|
|
||
| else: | ||
| print(f"Unsupported file type: {ext}") | ||
| sys.exit(2) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||||||||||||
| # Copyright (C) 2025 NV Access Limited, Noelia Ruiz Martínez | ||||||||||||||||||||||
| # This file is covered by the GNU General Public License. | ||||||||||||||||||||||
| # See the file COPYING for more details. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import os | ||||||||||||||||||||||
| import sys | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| sys.path.insert(0, os.getcwd()) | ||||||||||||||||||||||
| import buildVars | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def main(): | ||||||||||||||||||||||
| addonId = buildVars.addon_info["addon_name"] | ||||||||||||||||||||||
| name = "addonId" | ||||||||||||||||||||||
| value = addonId | ||||||||||||||||||||||
| with open(os.environ["GITHUB_OUTPUT"], "a") as f: | ||||||||||||||||||||||
|
||||||||||||||||||||||
| with open(os.environ["GITHUB_OUTPUT"], "a") as f: | |
| githubOutput = os.environ.get("GITHUB_OUTPUT") | |
| if not githubOutput: | |
| print( | |
| "Error: GITHUB_OUTPUT is not set. This script must be run in a GitHub Actions step " | |
| "with GITHUB_OUTPUT available.", | |
| file=sys.stderr, | |
| ) | |
| raise SystemExit(1) | |
| with open(githubOutput, "a") as f: |
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.
This will be run inside GitHub Actions.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ on: | |
| branches: [ main, master ] | ||
|
|
||
| workflow_dispatch: | ||
| workflow_call: | ||
|
|
||
| jobs: | ||
| build: | ||
|
|
||
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.
can you explain the point of calculating a score? I don't really understand why this exists. If the score is important, I don't think we should be relying on it. Some languages cannot be accurately detected via mechanisms like this, particularly when we support different dialects of the same language
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.
This has been removed. I don't know if Abdel will want to create a new PR. Translators prefer to use xliff.