Add scripts to allow addons from personal repos to be synchronized with Crowdin#1
Add scripts to allow addons from personal repos to be synchronized with Crowdin#1nvdaes wants to merge 77 commits intonvaccess:masterfrom
Conversation
… addedwith dev role to Crowdin if they use a project not owned by them to upload source files)
…flow to upload/update files in Crowdin
…k pass creating a PR at nvdaes/translateNvdaaddonsWithCrowdin repo
| strictSetInference = true | ||
|
|
||
| # Compliant rules | ||
| reportAbstractUsage = true |
There was a problem hiding this comment.
it's probably better to keep these rules than dropping to NVDA's standard
PurposeAdd-on authors may wish to help translators use Crowdin, the same framework where they translate NVDA. to translate messages and documentation for maintained add-ons: Other details
Development approachUse the Crowdin registration repo to add scripts usable by individual add-ons in personal repos. |
|
I've tested that all check pass using this pyproject.toml file on this PR: nvdaes/translateNvdaAddonsWithCrowdin#11 I use precommit, CodeQL and a workflow to check that all translatable messages have comments for translators. I'll try to use the cache action to cache some add-on metadata like its id, and also hashfiles from l10nSources (taking the value of buildVars.py), and the hasf¡hfile of the readme.md, to determine if pot and xliff files should be updated. |
|
Export translations to Crowdin running the workflow with update=False works properly: https://github.com/nvdaes/translateNvdaAddonsWithCrowdin/actions/runs/19802210157 |
|
This time, updatexLiff is failing. Seems that adding blank lines to readme may cause problems: https://github.com/nvdaes/translateNvdaAddonsWithCrowdin/actions/runs/19802391926/job/56731562709 |
|
If someone can help with this issue when update xliff, I'll be grateful. |
|
It might be easier to avoid xliff and just translate the markdown files directly. This won't support diffs very well but worth experimenting with |
|
@seanbudd wrote:
OK. |
|
@CyrilleB79, you were interested in this framework. If you want, feel free to see how the translateNvdaAddonsWithCrowdin.md can be translated in the project. Using xliff files is causing problems, as mentioned, and we are experimenting uploading md files instead. |
|
@seanbudd wrote:
I agree. Otherwise, we hav a lot of things to do in different projects. I'll request for volunteers for this. Good news is that I can print the list of members in the project via l10nUtil.exe. |
- move Python helper scripts from .github/workflows to .github/scripts for better separation of concerns - add polib dependency and switch to uv sync for reproducible CI environment - fix missing GH_TOKEN required for GitHub CLI (gh) commands - fix l10nUtil.exe path resolution (use ./l10nUtil.exe instead of _l10n/l10nUtil.exe) - improve Crowdin download behavior by avoiding processing empty translation files - refine PO handling: preserve local translations, conditionally upload to Crowdin when needed - refine XLIFF handling: update local documentation only, no upload back to Crowdin - ensure safer, more deterministic, and more predictable translation synchronization logic
The script is already available in .github/scripts/.
Improve crowdinL10n.yml translation workflow
|
pre-commit.ci run |
|
@seanbudd , I think this is ready for review. The workflow implemented by @abdel792 works well, though seems that xliff files may not work well for old add-ons, probably due to extra spaces in markdown files. We used a readme.md, and then we had to copy it to the svn repo with some modifications. I guess that something may be broken in the conversion or in the format, not sure. https://github.com/nvdaes/readFeeds/actions/runs/24792870808/job/72554821619 Also, I think that we will need to create a different PR to document how to upload pot and xliff files, including updating them if the markdown file is updated. |
…on logic - Add Markdown language scoring (langid) in checkTranslation.py - Extend script to support MD files and optional multi-file comparison - Update workflow to handle XLIFF → MD conversion only when translated - Implement multi-source comparison (XLIFF MD, remote MD, local MD) - Apply best-quality selection before updating or uploading files - Add full logging for all decision branches - Improve fallback behavior when only one source is available
Improve Crowdin localization workflow with intelligent MD/XLIFF handling
|
@seanbudd, @abdel792 has made more and significant improvements. This can be shown here https://github.com/nvdaes/readFeeds/actions/runs/24808570637/job/72608265836 I'll resolve conflicts and mark this as ready for review again. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| addonId = buildVars.addon_info["addon_name"] | ||
| name = "addonId" | ||
| value = addonId | ||
| with open(os.environ["GITHUB_OUTPUT"], "a") as f: |
There was a problem hiding this comment.
GITHUB_OUTPUT is accessed unconditionally; if this script is run outside GitHub Actions (or the env var is missing), it will raise a KeyError with a confusing traceback. Consider validating os.environ.get("GITHUB_OUTPUT") and exiting with a clear error message when it’s not set.
| 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.
This will be run inside GitHub Actions.
| Then, to export your add-on to Crowdin for the first time, run the `.github/workflows/exportAddonsToCrowdin.yml`, ensuring that the update option is set to false. | ||
| When you have updated messages or documentation, run the workflow setting update to true (which is the default option). |
There was a problem hiding this comment.
The workflow referenced here (.github/workflows/exportAddonsToCrowdin.yml) doesn’t exist in this PR; the added workflow is crowdinL10n.yml. Please update the documentation to reference the correct workflow name (and ensure the described inputs/options like update=false match what the workflow actually supports).
| uses: astral-sh/setup-uv@v7 | ||
|
|
||
| - name: Install dependencies | ||
| run: uv sync |
There was a problem hiding this comment.
uv sync will re-resolve dependencies if no lockfile is present, which can make CI non-reproducible (and slower). If the intent is to use locked deps, re-add uv.lock to the repo and run uv sync --frozen/--locked (or equivalent) so this workflow fails if the lock is out of date.
| run: uv sync | |
| run: uv sync --locked |
| if ($xliffValid) { | ||
| Write-Host "Converting XLIFF → MD" | ||
| $tempMd = "$env:TEMP\readme_$langCode.md" | ||
| ./l10nUtil.exe xliff2md $xliffFile $tempMd |
There was a problem hiding this comment.
After running l10nUtil.exe xliff2md, the script assumes $tempMd was created and later Move-Items it. If xliff2md fails (non-zero exit code or no output file), this can cause misleading behavior or runtime errors. Consider checking $LASTEXITCODE and Test-Path $tempMd before proceeding, and fail the step when conversion fails.
| ./l10nUtil.exe xliff2md $xliffFile $tempMd | |
| ./l10nUtil.exe xliff2md $xliffFile $tempMd | |
| if ($LASTEXITCODE -ne 0 -or -not (Test-Path $tempMd)) { | |
| throw "xliff2md failed for '$xliffFile' or did not create expected output '$tempMd'." | |
| } |
There was a problem hiding this comment.
I think this is not needed for this workflow.
| - name: Download l10nUtil | ||
| run: | | ||
| gh release download --repo nvaccess/nvdaL10n --pattern "l10nUtil.exe" | ||
|
|
||
| - name: Download translations from Crowdin | ||
| shell: pwsh | ||
| run: | | ||
| ./l10nUtil.exe exportTranslations -o _addonL10n -c addon | ||
|
|
There was a problem hiding this comment.
This step runs several external commands (gh release download, l10nUtil.exe exportTranslations, l10nUtil.exe uploadTranslationFile, etc.) but doesn’t enforce failure on non-zero exit codes. In PowerShell, most external-command failures won’t stop the script unless you check $LASTEXITCODE (or explicitly throw/exit). Recommend adding strict error handling so the workflow fails fast when downloads/exports/uploads fail.
|
|
||
| git switch ${{ env.downloadTranslationsBranch }} 2>$null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| git switch -c ${{ env.downloadTranslationsBranch }} | ||
| } | ||
|
|
||
| git push -f --set-upstream origin ${{ env.downloadTranslationsBranch }} |
There was a problem hiding this comment.
The workflow commits on the current branch, then switches/creates the l10n branch afterwards. If l10n already exists locally, git switch l10n will move HEAD away from the new commit and the subsequent push won’t include the translation update. Switch/create/reset the target branch before committing (or explicitly push the commit to l10n, e.g. git push origin HEAD:l10n).
| git switch ${{ env.downloadTranslationsBranch }} 2>$null | |
| if ($LASTEXITCODE -ne 0) { | |
| git switch -c ${{ env.downloadTranslationsBranch }} | |
| } | |
| git push -f --set-upstream origin ${{ env.downloadTranslationsBranch }} | |
| git push -f --set-upstream origin HEAD:${{ env.downloadTranslationsBranch }} |
There was a problem hiding this comment.
l10n shouldn't exis locally since the main branch will be checked out.
Blocked by #7