Skip to content

Refactor and tidy up#1

Open
nvdaes wants to merge 67 commits intonvaccess:mainfrom
nvdaes:addonTemplate
Open

Refactor and tidy up#1
nvdaes wants to merge 67 commits intonvaccess:mainfrom
nvdaes:addonTemplate

Conversation

@nvdaes
Copy link

@nvdaes nvdaes commented Jan 24, 2026

This pull request introduces significant improvements to the translation utilities for NVDA and its add-on template. The most important changes include a major refactor of the Crowdin integration logic in l10nUtil.py, the removal of the now-redundant crowdinSync.py, the addition of new dependency and build configuration in pyproject.toml, and enhancements to developer documentation and pre-commit configuration. These updates make the translation workflow more robust, flexible, and maintainable.

Crowdin integration refactor and CLI improvements:

  • Replaced hardcoded Crowdin file IDs with dynamic lookup and caching using a new JSON cache file, making file management more robust and scalable. Added new functions for uploading and downloading files, and improved error handling and logging throughout the Crowdin API interactions. [1] [2] [3]
  • Added new CLI commands and arguments to l10nUtil.py for uploading source files, specifying Crowdin project names/IDs, and improved flexibility for translation file operations. [1] [2] [3] [4] [5]
  • Removed the obsolete source/crowdinSync.py script, consolidating all Crowdin-related logic into l10nUtil.py.

Project configuration and dependency management:

  • Overhauled pyproject.toml to include project metadata, dependency management, build system configuration, and tool settings for ruff and pyright. Added a CLI entry point for l10nutil.
  • Updated .python-version to specify Python 3.13.11 for better reproducibility.
  • Enhanced .pre-commit-config.yaml with improved argument handling and added a hook for verifying the uv lock file. [1] [2]

Documentation:

  • Expanded readme.md with clear instructions for installing dependencies and building executables using uv and pyinstaller.

@nvdaes
Copy link
Author

nvdaes commented Jan 24, 2026

Description

This PR allows to create an executable to be used in the following projects available at Crowdin:

  • NVDA.
    • nvdaAddons, owned by me (nvdaes).
  • Other projects knowing the project ID.

Development approach

Methods available in the Crowdin API client have been used to upload source files to Crowdin, and to get the files available in the project, updating a json file progressively, i.e., when the user needs to know a file ID to run a command from the created executable, this utility checks the json file corresponding to the project ID (like NVDA, nvdaAddons etc.), and, if the file ID is not found, it request the File ID to the API. To avoid issues due to the size of the list of available files, we filted retrieved files to get just the needed ID, to update the json file in a progressive way appending needed file IDS.

Another significant difference is that the crowdinSync module has been removed. Instead, we use the Crowdin API client to add or update source files.

There are other commands available.

@nvdaes
Copy link
Author

nvdaes commented Jan 24, 2026

@seanbudd , I've created this PR to allow NV Access to review proposed differences.

Feel free to review or to close this in case you prefer a different approach.

@nvdaes
Copy link
Author

nvdaes commented Jan 24, 2026

Command to create the executable file, which will be placed in a dist subfolder:

pyInstaller --onefile source/l10nUtil.py

After creating the executable, find it in /dist/l10nUtil.exe.

Copy link
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.

I'm very confused about the large amount of behaviour changes here. Can you please make a more detailed PR description outlining these changes, and consider splitting up this PR into smaller pieces?

# External links will open in a new tab, and title will be set to the link text
"markdown_link_attr_modifier",
# Adds links to GitHub authors, issues and PRs
"mdx_gh_links",
Copy link
Member

Choose a reason for hiding this comment

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

what happened to LaTeX2MathMLExtension?

Copy link
Author

Choose a reason for hiding this comment

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

Sorry, all changes of this file are accidental. I wanted to change just l10nUtil, and remove crowdinSync

Comment on lines 128 to 146
if "math" not in allowedAttributes:
allowedAttributes["math"] = set()
allowedAttributes["math"].add("display")

return allowedAttributes


def _attributeFilter(tag: str, attr: str, value: str) -> str | None:
# Specifying display=inline is redundant
if tag == "math" and attr == "display":
return value if value == "block" else None
return value


def _createTagFilter() -> set[str]:
import nh3

return nh3.ALLOWED_TAGS | {"math", "mrow", "mfrac", "mi", "mn", "mo", "msub"}

Copy link
Member

Choose a reason for hiding this comment

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

why can this be removed?

Comment on lines 60 to 63
},
"pymdownx.superfences": {
"preserve_tabs": True,
},
Copy link
Member

Choose a reason for hiding this comment

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

are you sure this can be removed?

pyproject.toml Outdated
Comment on lines 159 to 201
reportDeprecated = false # 1834 errors

# Can be enabled by generating type stubs for modules via pyright CLI
reportMissingTypeStubs = false

# Bad rules
# These are roughly sorted by compliance to make it easier for devs to focus on enabling them.
# Errors were last checked Feb 2025.
# 1-50 errors
reportUnsupportedDunderAll = false # 2 errors
reportAbstractUsage = false # 3 errors
reportUntypedBaseClass = false # 4 errors
reportOptionalIterable = false # 5 errors
reportCallInDefaultInitializer = false # 6 errors
reportInvalidTypeArguments = false # 7 errors
reportUntypedNamedTuple = false # 11 errors
reportRedeclaration = false # 12 errors
reportOptionalCall = false # 16 errors
reportConstantRedefinition = false # 18 errors
reportWildcardImportFromLibrary = false # 26 errors
reportIncompatibleVariableOverride = false # 28 errors
reportInvalidTypeForm = false # 38 errors


# 50-100 errors
reportGeneralTypeIssues = false # 53 errors
reportOptionalOperand = false # 59 errors
reportUnnecessaryComparison = false # 67 errors
reportFunctionMemberAccess = false # 80 errors
reportUnnecessaryIsInstance = false # 88 errors
reportUnusedFunction = false # 97 errors
reportImportCycles = false # 99 errors
reportUnusedImport = false # 113 errors
reportUnusedVariable = false # 147 errors

# 100-1000 errors
reportOperatorIssue = false # 102 errors
reportAssignmentType = false # 103 errors
reportReturnType = false # 104 errors
reportPossiblyUnboundVariable = false # 126 errors
reportMissingSuperCall = false # 159 errors
reportUninitializedInstanceVariable = false # 179 errors
reportUnknownLambdaType = false # 196 errors
Copy link
Member

Choose a reason for hiding this comment

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

can't we make these true here?

Copy link
Author

Choose a reason for hiding this comment

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

Now all is true. Let's see what errors. Now replying from the new interface when using the files changed link to review and leave comments seems not to be accessible or at least I don't find it, though the interface itself is nice. So I'm switching from files changed to the main conversation. Sorry if I reply to the wrong comment, but this is for pyproject.toml.

@@ -1,89 +0,0 @@
# A part of NonVisual Desktop Access (NVDA)
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't this be necessary?

Copy link
Author

Choose a reason for hiding this comment

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

According to my tests, this file wouldn't be necessary.

Copy link
Member

Choose a reason for hiding this comment

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

how else will we sync with crowdin in NVDA and the addontemplate?

Copy link
Author

Choose a reason for hiding this comment

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

For now the uploadSourceFile command is just available for the nvdaAddons project since I was not sure if you would prefer to use crowdinSync or not. I can create a separate pull request, but this may take some days.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand. I think we want to ensure all the shared code between NVDA and addonTemplate is in the l10nUtil. Don't we need uploadSourceFile from this file in both repos?

Copy link
Author

Choose a reason for hiding this comment

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

No, uploadSourceFile is a command added to the l10nUtil file without using custom code, but using the standard methods from the API. I thought that this would be more compact and maintainable. So when creating the executable, uploadSourceFile is just another command. But I didn't know if you would like to use crowdinSync or just my approach, so I have added this just for the nvdaAddons project. I'll make it available for any other projects and you may want to test it with NVDA.

@seanbudd
Copy link
Member

can you please make sure to review the diff and ensure only intended changes are included. moving functions and blocks around for no clear reason makes reviewing the diff harder.

@seanbudd seanbudd marked this pull request as draft February 17, 2026 01:22
@nvdaes
Copy link
Author

nvdaes commented Feb 17, 2026

Sean wrote:

can you please make sure to review the diff and ensure only intended changes are included. moving functions and blocks around for no clear reason makes reviewing the diff harder.

I think it's done now. I've reviewed diffs in l10nUtil.py with the -w flag to ignore tabs and spaces, since nany differences are caused to format.
Then I've built the executable with pyinstaller folliwing instructions to build it from the readme.

@nvdaes nvdaes marked this pull request as ready for review February 17, 2026 20:33
pyproject.toml Outdated

[project]
name = "nvdaL10n"
name = "nvdal10n"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
name = "nvdal10n"
name = "nvdaL0n"

Copy link
Author

Choose a reason for hiding this comment

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

I assume that you mean nvdaL10n, right?

Copy link
Member

Choose a reason for hiding this comment

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

yes oops

pyproject.toml Outdated
description = "Utilities for translations in NVDA and the add-on template"
maintainers = [
{name = "NV Access", email = "info@nvaccess.org"},
{name = "Noelia Ruiz Martínez", email = "nrm1977@gmail.com"},
Copy link
Member

Choose a reason for hiding this comment

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

can we keep this as NV Access?

pyproject.toml Outdated
Comment on lines 200 to 206
# Bad rules
# These are sorted alphabetically and should be enabled and moved to compliant rules section when resolved.
lint = [
"ruff==0.8.1",
"pre-commit==4.0.1",
"pyright==1.1.396",
]
Copy link
Member

Choose a reason for hiding this comment

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

can this be moved with other dependencies and the comment removed?

pyproject.toml Outdated
Comment on lines 93 to 80
venvPath = ".venv"
venvPath = "../nvda/.venv"
Copy link
Member

Choose a reason for hiding this comment

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

I think we should be using the venv set up for the add-on template. this change should be undone

pyproject.toml Outdated
Comment on lines 98 to 102
# Tell pyright where to load python code from
extraPaths = [
"../addon",
"../nvda/source",
]
Copy link
Member

Choose a reason for hiding this comment

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

is this necessary?

pyproject.toml Outdated

[project.optional-dependencies]
windows-packaging = [
"py2exe==0.11.1.0",
Copy link
Member

Choose a reason for hiding this comment

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

is py2exe used anymore?



_crowdinClient = None
_crowdinProjectId = 598017
Copy link
Member

Choose a reason for hiding this comment

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

let's remove this, and only use the project ID passed in via CLI.

also, sorry to make things more complicated, but I was thinking this might be the best way to handle the different configs required for different projects.

We could use a YAML file as config that includes the project ID and a mapping of file IDs and file names.
We could include YAML config files for both the add-on template and NVDA in the distribution of this. That way instead people could just do nvdaL10nUtil.exe -c [nvda.yaml|addon.yaml] [command] <file>. This also means its more easily customisable for other projects. I'm worried getFiles won't work due to the aliases we need for .po/.pot

Copy link
Author

Choose a reason for hiding this comment

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

OK. Then should we use _crowdinProjectId = None? to set the global variable according to the -i flag? Should we encapsulate _crowdinClient and _crowdinProjectId in a dataclass named, for example, ClientVariables or something? For the yaml file, can you provide the exact structure? Should we try to dump values to cache them from getFiles, like done with json files previously? I guess that yaml is more readable than json and easy to write.

Copy link
Member

Choose a reason for hiding this comment

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

I think we should encapsulate it into a dataclass, one which can be loaded from the yaml config file.
JSON is also fine for config, either is good to me.

e.g.

{
"crowdinProjectId": 93,
"files": {"nvda.po": 12}
}

Copy link
Author

Choose a reason for hiding this comment

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

Perhaps the yaml file is easier to edit. Should we restore the ability to update the yaml (or json) file from the getFiles function? Otherwise, I don't know how these files will be created or updated.

Copy link
Member

Choose a reason for hiding this comment

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

The idea is to create the config files manually as maintainers, and ship the config files with the l10n exe

Copy link
Member

Choose a reason for hiding this comment

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

ah okay - do you think this could be generated via a command?

Copy link
Author

Choose a reason for hiding this comment

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

I think this cannot be generated via a single command, since, if we call getFiles and try to write the full list in a yaml file, the list may be too long and we may have issues with the connection. I would restore the previous behavior of the getFiles function if you agree, i. e., trying to update the yaml file when getFiles is called with the filter parameter, so that file ids are added progressively. Then, the getFiles function may try to retrieve files from the yaml file before using the Crowdin API, and just if a refresh parameter is set to True, or if files aren't found in the yaml file, the Crowdin API is used. Please let me know if this is the right approach.

Copy link
Member

Choose a reason for hiding this comment

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

I'm suggesting we generate config files when creating l10nUtil.exe not when running it for the first time. However, I imagine this may cause problems, as people would need to sync with the latest config file somehow from their add-on repository

Copy link
Author

Choose a reason for hiding this comment

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

My suggestion would be to create configuration files when translators use this tool for add-ons, and when the exe file is created for NVDA. There are a lot of add-ons, but NVDA has just a few files that can be reflected in the yaml file. If you want, I can design a prototype, perhaps in a different PR or just here.

Copy link
Member

Choose a reason for hiding this comment

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

that sounds good to me. We can improve the design later if we need

nvdaes and others added 3 commits February 18, 2026 21:50
@nvdaes
Copy link
Author

nvdaes commented Feb 18, 2026

@seanbudd , I've removed py2exe and addressed your feedback, except for the yaml configuration. I'll wait for your clarifications.

@seanbudd seanbudd self-assigned this Feb 19, 2026
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.

3 participants