Skip to content

Add scripts to allow addons from personal repos to be synchronized with Crowdin#1

Open
nvdaes wants to merge 131 commits intonvaccess:masterfrom
nvdaes:l10n
Open

Add scripts to allow addons from personal repos to be synchronized with Crowdin#1
nvdaes wants to merge 131 commits intonvaccess:masterfrom
nvdaes:l10n

Conversation

@nvdaes
Copy link
Copy Markdown

@nvdaes nvdaes commented Nov 24, 2025

This pull request introduces a complete, automated workflow for synchronizing translations with Crowdin, including new scripts, a scheduled GitHub Actions workflow, and supporting documentation. The main changes add Python and PowerShell scripts for translation status checking and synchronization, a workflow for scheduled and manual Crowdin sync, language mapping configuration, and documentation updates. These improvements enable seamless translation management for add-on projects.

Crowdin Synchronization Workflow and Automation:

  • Added .github/workflows/crowdinL10n.yml to automate translation synchronization with Crowdin, running weekly and on demand. It sets up the environment, downloads required tools, and triggers the sync process.
  • Introduced .github/scripts/crowdinSync.ps1, a comprehensive PowerShell script that updates source files, uploads them to Crowdin, exports translations, evaluates translation quality, and commits updates back to the repository.

Translation Quality and Language Mapping Utilities:

  • Added .github/scripts/checkTranslation.py, a Python script that checks translation progress for a given file and language using the Crowdin API, supporting quality thresholds for importing translations.
  • Added .github/scripts/languageMappings.json to map local language codes to Crowdin-compatible codes, ensuring correct language matching during synchronization.
  • Added .github/scripts/setOutputs.py, a Python script to extract and expose the add-on ID for use in workflows.

Configuration and Documentation:

  • Updated pyproject.toml to include new scripts in the exclude list for packaging and removed the unused requests dependency. [1] [2]
  • Updated readme.md with detailed instructions on setting up and using the Crowdin translation workflow, including project setup and required secrets.
  • Added .python-version specifying Python 3.13 for consistent workflow environments.

These changes collectively provide a robust, maintainable infrastructure for managing translations in add-on projects with Crowdin.

Comment thread pyproject.toml
@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Nov 28, 2025

Purpose

Add-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

  • Pot file are created/updated, and uploaded to a Crowdin project.
  • The readme.md file is converted to xliff and uploaded to a Crowdin project.
  • Po and xfiles are translated.
  • Translated files are downloaded and processed to be copied to locale/langCode/LC_MESSAGES/nvda.po, and doc/langCode/readme.md, in the addon folder.
    Authors need to store a Crowdin token with permissions to upload files to the Crowdin project as a repository secret.

Development approach

A workflow (GitHub Actions), and several scripts (Python and Powershell), as well as a json file with language mappings have been added.
Also NV Access l10nUtil.exe is used to synchronize files between the add-on and Crowdin, and to convert between xliff and markdown formats.

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Nov 28, 2025

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.

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Nov 30, 2025

Export translations to Crowdin running the workflow with update=False works properly:

https://github.com/nvdaes/translateNvdaAddonsWithCrowdin/actions/runs/19802210157

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Nov 30, 2025

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

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Nov 30, 2025

If someone can help with this issue when update xliff, I'll be grateful.
I think that this is one of the bugest problems with xliff files. Sometimes sel lines are None and they don't have a strip method. I don't know if this should be also improved in NVDA
cc: @seanbudd

@seanbudd
Copy link
Copy Markdown
Member

seanbudd commented Dec 1, 2025

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

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Dec 1, 2025

@seanbudd wrote:

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

OK.

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Dec 3, 2025

@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.

@abdel792
Copy link
Copy Markdown

abdel792 commented Apr 29, 2026

Hi @nvdaes,

Thanks for the updates! I noticed a small bug in the last commit that triggers an API ERROR: 'language_id'.

It seems that when converting variables to camelCase, the dictionary key for the Crowdin API response was changed to language_id (line 98).

However, the Crowdin API returns this specific key as languageId.

I've tested it by changing it back to langApi = item["data"]["languageId"] and it works perfectly again.

Do you want me to push the fix or will you handle it?

Thanks! ☺

The recent refactoring to camelCase accidentally changed the dictionary key 'languageId' to 'language_id' when accessing the API response data. Since the Crowdin API strictly returns 'languageId', this caused a KeyError.

- Restored 'languageId' in getScoreFromApi function to ensure proper score retrieval[cite: 2].
- Verified that other internal camelCase variables remain unchanged to satisfy NVDA coding standards[cite: 2].
@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Apr 29, 2026

@abdel792 wrote:

Do you want me to push the fix or will you handle it?

Please fix it yourself. Do you want to test it in one of your add-ons, or should I perform the final test?

@abdel792
Copy link
Copy Markdown

abdel792 commented Apr 29, 2026

Hi @nvdaes,

I've just pushed a fix for the API ERROR: 'language_id'.

During the camelCase refactoring, the dictionary key for the Crowdin API response was changed to language_id (line 98). However, since this is a key returned directly by the Crowdin API, it must remain languageId to be correctly mapped.

I have:

  • Restored item["data"]["languageId"] to fix the KeyError.
  • Kept the internal variable name as langApi (camelCase) to respect NVDA standards[cite: 1].

The script is now working perfectly again for all file types (.po, .md, and .xliff). Sorry for missing that during the initial review! ☺

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Apr 29, 2026

Thanks so much. I'l perform the final test.

@abdel792
Copy link
Copy Markdown

Hi @nvdaes,

I've just pushed the fix for languageId.

I am currently testing the script directly on my PC using PowerShell, as I mentioned before. I'm iterating through my local add-on directories and querying the Crowdin API for each one to verify the scores. Everything is working as expected now!

You can perform a final test on your side if you wish, but from my end, the script is fully functional and ready for the final review. ☺

@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Apr 29, 2026

I'll perform a final test to show publicly that it works. Thanks for your work!

@nvdaes nvdaes marked this pull request as ready for review April 29, 2026 16:26
@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Apr 29, 2026

@abdel792 , I've run pre-commit to fix chekcs.
@seanbudd I think this is ready for review.
Please see this test:

https://github.com/nvdaes/readFeeds/actions/runs/25120306831/job/73618870755

@abdel792
Copy link
Copy Markdown

abdel792 commented Apr 29, 2026

Thanks @nvdaes for running the pre-commit fixes! Glad to see the checks are passing now. Looking forward to @seanbudd's review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/scripts/crowdinSync.ps1 Outdated
Comment thread readme.md
Comment thread readme.md Outdated
Comment thread pyproject.toml Outdated
Comment thread .github/workflows/crowdinL10n.yml
Comment thread .github/workflows/crowdinL10n.yml
Comment thread .github/scripts/crowdinSync.ps1 Outdated
Comment thread .github/scripts/crowdinSync.ps1 Outdated
nvdaes and others added 6 commits April 30, 2026 17:59
Remove temp file as a general good practice, though this is not needed since this is intended just to be used with GitHub Actions.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@nvdaes
Copy link
Copy Markdown
Author

nvdaes commented Apr 30, 2026

@seanbudd and @abdel792 I've updated the readme and addressed Copilot suggestions.
In particular:

  • I've added the add-ons mailing list in addition to the translations list to request an invitation as developers for add-on authors.
    • Adviced authors to edit the cron line of workflows, so their API token is not used concurrently in different repos to prevent failures. For any reason, I'm observing that, lastly, the first time I try to run the workflow in the readFeeds add-on, it's failing with a message related to API request, and the last time when a failure has happened, I've read that another build (of exporting translation) was happening. I don't know why.
      Also, I'm seing that the ne language is not present in Crowdin, but this is not preventing the workflow to success. I don't know what language should be added in crowding for ne, or if a mapping is needed. But this is not affecting this PR.

See the last test at

https://github.com/nvdaes/readFeeds/actions/runs/25177842831/job/73814743331

@abdel792
Copy link
Copy Markdown

Hi @nvdaes,
Great job addressing the Copilot suggestions! Unifying the secret names in the README and workflows was definitely a necessary fix to avoid confusion for new users.
Regarding the 409 error, your advice to change the cron schedule is a smart workaround to prevent API rate-limiting across different repos.
I've reviewed your latest changes and the latest test run on readFeeds. Everything looks solid and ready to go. Thanks for the final polish!

Copy link
Copy Markdown
Member

@seanbudd seanbudd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I haven't thoroughly reviewed .github/scripts. I need a clearer PR description to understand the purpose and design of these

Comment thread pyproject.toml Outdated
# When excluding concrete paths relative to a directory,
# not matching multiple folders by name e.g. `__pycache__`,
# paths are relative to the configuration file.
".github/scripts/markdownTranslate.py",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we block this whole folder? .github/scripts

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread .github/scripts/setOutputs.py Outdated
Comment thread .github/scripts/markdownTranslate.py Outdated
Comment thread readme.md
Comment thread readme.md
Comment on lines +193 to +195
Ensure that your repository includes the following files (provided in this template):
* **Workflows:** `.github/workflows/crowdinL10n.yml`
* **Scripts:** The `.github/scripts/` folder containing `checkTranslation.py`, `markdownTranslate.py`, `languageMappings.json`, `setOutputs.py`, and `crowdinSync.ps1`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be included automatically via the template?

Comment thread readme.md

The translation workflow will be run weekly. Also, you can run the workflow manually from GitHub or using GitHub CLI.

If you have various add-ons, please edit the cron line of workflows in each repo, so that your API token is not used at the same time.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could add a random 0-5 min sleep at the start of the job to offset them?

Comment thread .github/workflows/crowdinL10n.yml Outdated
Comment thread .github/scripts/checkTranslation.py Outdated
Comment thread .github/scripts/checkTranslation.py Outdated
@@ -0,0 +1,148 @@
# checkTranslation.py
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still not clear to me what the purpose of this file is. Can you please update the PR description to describe the changes in this PR and why?
I've set it to copilot's output for now.
TODO: review this file later

Comment on lines +122 to +140
# --- 3.2 DOCUMENTATION PROCESSING (MD & XLIFF) ---
$scoreMd = 0.0
$scoreXliff = 0.0

if (Test-Path $remoteMd) {
Write-Host "DEBUG: Evaluating Remote Markdown score..."
$res = uv run python .github/scripts/checkTranslation.py "$addonId.md" $crowdinLang
$scoreMd = [double]($res | Select-String "mdScore=").ToString().Split("=")[1]
} else {
Write-Host "DEBUG: No remote Markdown file found for this language."
}

if (Test-Path $remoteXliff) {
Write-Host "DEBUG: Evaluating Remote XLIFF score..."
$res = uv run python .github/scripts/checkTranslation.py "$addonId.xliff" $crowdinLang
$scoreXliff = [double]($res | Select-String "xliffScore=").ToString().Split("=")[1]
} else {
Write-Host "DEBUG: No remote XLIFF file found for this language."
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these scores for?

nvdaes and others added 6 commits May 4, 2026 21:25
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants