Skip to content

Commit ac2b9d5

Browse files
committed
Merge remote-tracking branch 'remotes/origin/master' into scons4.8.1to4.9.1
2 parents a8b4a6b + 558905f commit ac2b9d5

File tree

129 files changed

+201768
-197861
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+201768
-197861
lines changed

projectDocs/translating/crowdin.md

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,91 @@
1-
# Translating using Crowdin
1+
# Translating with Crowdin
22

3-
Crowdin is used to translate the main NVDA interface and user documentation.
3+
Crowdin is used to manage the translations of the main NVDA interface and user documentation.
44
NVDA's Crowdin project: <https://crowdin.com/project/nvda>.
55

6-
This document covers setting up a Crowdin account, connecting it with PoEdit, and translating the main interface and user documentation using Crowdin and PoEdit.
6+
This document covers setting up a Crowdin account, and translating the main interface and user documentation using either the Crowdin web interface or Poedit / nvdaL10nUtil.
77

8-
## Setup
9-
10-
### Create Crowdin account
8+
## Creating a Crowdin account
119

1210
1. Create a [Crowdin account](https://accounts.crowdin.com/register?continue=%2Fproject%2Fnvda).
1311
1. Message the [translators mailing list](https://groups.io/g/nvda-translations) or <[email protected]> to request being added as a translator.
1412
Please include your Crowdin username and the languages you wish to translate.
1513

16-
### Setup PoEdit
14+
## Translation workflows
15+
16+
There are 2 common workflows for translating with Crowdin:
17+
18+
1. Translating strings directly via Crowdin's web interface. Or
19+
1. Downloading a translation file from Crowdin, translating with Poedit, and uploading the file back to Crowdin.
20+
21+
### Translating via Crowdin's web interface
22+
23+
For instructions on how to translate strings using Crowdin's web interface, please read [Crowdin's documentation for translators](https://support.crowdin.com/for-translators/).
24+
25+
Note: If you are a screen reader user, you may find Crowdin's web interface inefficient or impossible to use.
26+
Therefore you may choose to use the Poedit workflow instead.
27+
28+
#### Translation Reviews / Approvals
29+
30+
Due to accessibility issues and to be able to support the alternative workflow using Poedit, the translation approvals feature has been disabled in the NVDA project on Crowdin.
31+
This means that all new strings are essentially auto approved.
32+
However, to maintain quality control, only translators specifically added to the project can add or change strings.
33+
34+
### Translating with Poedit
35+
36+
Poedit is a desktop application which is commonly used to translate files.
37+
It is fairly accessible and is used by many blind and vision impaired translators.
38+
39+
The workflow for translating with Poedit involves:
40+
1. Downloading the translation file using NVDA's l10n utility
41+
1. Opening the translation file in Poedit, translating one or more strings, and saving the file.
42+
1. Uploading the translation file back to Crowdin.
43+
44+
Warning: Do not download / upload translation files via Crowdin's web interface, nor use Poedit's Crowdin cloud translation feature, as these methods have been found to corrupt xliff files.
45+
Please instead use NvDA's l10n utility for download and upload.
46+
NVDA's l10n utility will automatically use the appropriate Crowdin settings when downloading and uploading, and will detect and correct corruptions in xliff files.
47+
48+
#### Setting up PoEdit
1749

1850
It is recommended that you use the latest version of PoEdit and NVDA for translating.
19-
Alternatively, you can use the [Crowdin web interface](https://support.crowdin.com/online-editor/) directly.
20-
As PoEdit only supports viewing approved strings, large translator teams need to co-ordinate submitting unapproved strings to prevent conflicts.
21-
Using Crowdin's interface avoids this problem.
2251

2352
PoEdit's homepage is: <http://www.poedit.net/>
2453

2554
1. Download the latest Windows PoEdit version at <https://poedit.net/download>
2655
1. Install it by following the on-screen instructions, the default options should be sufficient.
2756

28-
### Translation reviews
29-
Due to accessibility issues, for now translation approvals have been disabled on Crowdin.
30-
Any translation uploaded to Crowdin is automatically available in the project.
31-
However, joining the project as a translator is by invitation only.
57+
#### Locating the NVDA l10n utility
3258

33-
## Translation workflows
59+
The NVDA l10n utility is required for safely and efficiently downloading and uploading translation files from / to Crowdin when translating with Poedit.
3460

35-
There are 2 common workflows for translating with Crowdin:
61+
This utility is included with all versions of NVDA from 2025.1beta1 onwards.
3662

37-
1. Translating strings directly via Crowdin's interface. Or
38-
1. Downloading from Crowdin, translating with Poedit and uploading again.
63+
* For installed copies of NVDA, its path is: `c:\Program Files (x86)\nvda\l10nUtil.exe`
64+
* For portable copies, it can be found as `l10nUtil.exe` in the root directory of the portable copy.
3965

40-
## Translating using PoEdit
66+
#### Downloading po / xliff files with NVDA's l10n utility
4167

42-
After opening a .po or .xliff file you will be placed on a list with all of the strings to translate.
68+
```sh
69+
l10nUtil.exe downloadTranslationFile <language> <crowdinFilePath> [<localFilePath>]
70+
```
71+
72+
E.g.
73+
74+
```sh
75+
l10nUtil.exe downloadTranslationFile fr nvda.po
76+
```
77+
The first time you will be asked for an authorization token.
78+
Please visit [your Crowdin settings API page](https://crowdin.com/settings#api-key) and create a Personal Access Token.
79+
Ensure that it has at least the translations scope.
80+
Then paste this into the user prompt.
81+
This will be saved in ~/.nvda_crowdin for future use.
82+
83+
If your language team has more than one translator who may be downloading, translating and uploading at the same time, it is important that once you have downloaded the file, that you save a copy before you start translating with Poedit.
84+
This then allows you to provide l10nUtil with this original file when uploading, so that it can just upload only what has changed, which will avoid accidentally overriding another translator's work.
85+
86+
#### Translating po / xliff files using Poedit
87+
88+
After opening a .po or .xliff file you have previously downloaded with NVDA's l10n utility, you will be placed on a list with all of the strings to translate.
4389

4490
You can read the status bar to see how many strings have already been translated, the number of untranslated messages, and how many are fuzzy.
4591
A fuzzy string is a message which has been automatically translated, thus it may be wrong.
@@ -51,7 +97,7 @@ NVDA will beep if you are on an untranslated or fuzzy message.
5197
If you are using a braille display you'll see a star sign in-front of the messages you have to translate.
5298

5399
You may want to spell the original string to be aware of any punctuation mark, capital letters, etc.
54-
PoEdit has a keystroke you may press while on an original string, `alt+c`, that copies the original string to the edit field when pressed.
100+
To copy the original string into the "Translated text" control ready for further translation, you can press `control+b`.
55101
You may then replace it with your translation as normal.
56102

57103
Press `control+s` at any moment to save your work.
@@ -62,32 +108,33 @@ NVDA provides additional shortcuts for PoEdit which are described in [the User G
62108
If you are unsure of the meaning of the original interface message, consult automatic comments (also called translator comments), by pressing `control+shift+a`.
63109
Some comments provide an example output message to help you understand what NVDA will say when speaking or brailling such messages.
64110

65-
## Translating NVDA's interface
111+
#### Uploading po / xliff files with NvDA's l10n utility
112+
113+
After translating the file with Poedit, upload the file back to Crowdin.
66114

67-
* Download the po file for your language from Crowdin using NVA's l10nUtil.exe:
68115
```
69-
l10nUtil.exe downloadTranslationFile <language> <crowdinFilePath> [<localFilePath>]
116+
l10nUtil.exe uploadTranslationFile <language> <crowdinFilePath> [<localFilePath>]
70117
```
71118
E.g.
72119
```
73-
l10nUtil.exe downloadTranslationFile fr nvda.po
74-
```
75-
The first time you will be asked for an authorization token.
76-
Please visit [your Crowdin settings API page](https://crowdin.com/settings#api-key) and create a Personal Access Token.
77-
Ensure that it has at least the translations scope.
78-
Then paste this into the user prompt.
79-
This will be saved in ~/.nvda_crowdin for future use.
80-
* Open the xliff file in Poedit, translate, and save the file.
81-
* Upload the translated file using l10nUtil
82-
```
83-
l10nUtil.exe uploadTranslationFile <language> <crowdinFilePath> [<localFilePath>]
120+
l10nUtil.exe uploadTranslationFile fr nvda.po
84121
```
122+
123+
If you had previously saved a copy of the downloaded file before translation, then you will also want to include this in the command, so that l10nUtil only uploads the strings you have actually changed:
85124
E.g.
86125
```
87-
l10nUtil.exe uploadTranslationFile fr nvda.po
126+
l10nUtil.exe uploadTranslationFile fr nvda.po --old nvda_old.po
88127
```
128+
Where `nvda_old.po` was the saved copy.
129+
130+
## Translating NVDA's interface
131+
132+
* If translating via the Crowdin web interface, you can find these strings in the `NVDA interface messages` file.
133+
* If translating via Poedit, you can download it as `nvda.po` using NVDA's l10n utility, e.g.
89134

90-
Alternatively, you can use the [Crowdin interface directly](https://support.crowdin.com/online-editor/).
135+
```sh
136+
l10nUtil.exe downloadTranslationFile fr nvda.po
137+
```
91138

92139
### Messages with formatting strings
93140

@@ -180,34 +227,10 @@ Documentation available for translation includes:
180227
* The NVDA user guide (userGuide.xliff)
181228
* The NVDA What's New document (changes.xliff)
182229

183-
To translate any of these files:
184-
185-
* Download the xliff file for your language from Crowdin using NVA's l10nUtil.exe:
186-
```
187-
l10nUtil.exe downloadTranslationFile <language> <crowdinFilePath> [<localFilePath>]
188-
```
189-
E.g.
190-
```
191-
l10nUtil.exe downloadTranslationFile fr userGuide.xliff
192-
```
193-
The first time you will be asked for an authorization token.
194-
Please visit [your Crowdin settings API page](https://crowdin.com/settings#api-key) and create a Personal Access Token.
195-
Ensure that it has at least the translations scope.
196-
Then paste this into the user prompt.
197-
This will be saved in ~/.nvda_crowdin for future use.
198-
* Make a copy of the downloaded file.
199-
* Open the xliff file in Poedit, translate, and save the file.
200-
* Upload the translated file using l10nUtil
201-
```
202-
l10nUtil.exe uploadTranslationFile <language> <crowdinFilePath> [<localFilePath>] [--old <oldLocalFilepath>]
203-
```
204-
E.g.
205-
```
206-
l10nUtil.exe uploadTranslationFile fr userGuide.xliff --old userGuide_backup.xliff
207-
```
208-
This will only upload added / changed translations since you downloaded the file.
230+
To translate any of these files either:
209231

210-
Alternatively, you can use the [Crowdin interface directly](https://support.crowdin.com/online-editor/).
232+
* locate the file in the Crowdin web interface and translate directly, or
233+
* Download the file using NVDA's l10n utility, open the file in Poedit, translate and save, then upload the file with NVDA's l10n utility.
211234

212235
### Translating markdown
213236

source/_remoteClient/client.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from logHandler import log
2121
from gui.guiHelper import alwaysCallAfter
2222
from utils.security import isRunningOnSecureDesktop
23+
from gui.message import MessageDialog, DefaultButton, ReturnCode, DialogType
2324
import scriptHandler
2425

2526
from . import configuration, cues, dialogs, serializer, server, urlHandler
@@ -120,7 +121,7 @@ def toggleMute(self):
120121
"""
121122
if not self.isConnected():
122123
# Translators: Message shown when attempting to mute the remote computer when no session is connected.
123-
ui.message(pgettext("remote", "Not connected"))
124+
ui.delayedMessage(pgettext("remote", "Not connected"))
124125
return
125126
self.localMachine.isMuted = not self.localMachine.isMuted
126127
self.menu.muteItem.Check(self.localMachine.isMuted)
@@ -129,7 +130,7 @@ def toggleMute(self):
129130
# Translators: Displayed when unmuting speech and sounds from the remote computer
130131
UNMUTE_MESSAGE = _("Unmuted remote")
131132
status = MUTE_MESSAGE if self.localMachine.isMuted else UNMUTE_MESSAGE
132-
ui.message(status)
133+
ui.delayedMessage(status)
133134

134135
def pushClipboard(self):
135136
"""Send local clipboard content to the remote computer.
@@ -140,19 +141,19 @@ def pushClipboard(self):
140141
connector = self.followerTransport or self.leaderTransport
141142
if not getattr(connector, "connected", False):
142143
# Translators: Message shown when trying to send the clipboard to the remote computer while not connected.
143-
ui.message(pgettext("remote", "Not connected"))
144+
ui.delayedMessage(pgettext("remote", "Not connected"))
144145
return
145146
elif self.connectedClientsCount < 1:
146147
# Translators: Reported when performing a Remote Access action, but there are no other computers in the channel.
147-
ui.message(pgettext("remote", "No one else is connected"))
148+
ui.delayedMessage(pgettext("remote", "No one else is connected"))
148149
return
149150
try:
150151
connector.send(RemoteMessageType.SET_CLIPBOARD_TEXT, text=api.getClipData())
151152
cues.clipboardPushed()
152153
except (TypeError, OSError):
153154
log.debug("Unable to push clipboard", exc_info=True)
154155
# Translators: Message shown when clipboard content cannot be sent to the remote computer.
155-
ui.message(pgettext("remote", "Unable to send clipboard"))
156+
ui.delayedMessage(pgettext("remote", "Unable to send clipboard"))
156157

157158
def copyLink(self):
158159
"""Copy connection URL to clipboard.
@@ -162,10 +163,12 @@ def copyLink(self):
162163
session = self.leaderSession or self.followerSession
163164
if session is None:
164165
# Translators: Message shown when trying to copy the link to connect to the remote computer while not connected.
165-
ui.message(pgettext("remote", "Not connected"))
166+
ui.delayedMessage(pgettext("remote", "Not connected"))
166167
return
167168
url = session.getConnectionInfo().getURLToConnect()
168169
api.copyToClip(str(url))
170+
# Translators: A message indicating that a link has been copied to the clipboard.
171+
ui.delayedMessage(_("Copied link"))
169172

170173
def sendSAS(self):
171174
"""Send Secure Attention Sequence to remote computer.
@@ -191,6 +194,7 @@ def connect(self, connectionInfo: ConnectionInfo):
191194
elif connectionInfo.mode == ConnectionMode.FOLLOWER:
192195
self.connectAsFollower(connectionInfo)
193196

197+
@alwaysCallAfter
194198
def disconnect(self):
195199
"""Close all active connections and clean up resources.
196200
@@ -199,6 +203,36 @@ def disconnect(self):
199203
if self.leaderSession is None and self.followerSession is None:
200204
log.debug("Disconnect called but no active sessions")
201205
return
206+
207+
if (
208+
self.followerSession is not None
209+
and configuration.getRemoteConfig()["ui"]["confirmDisconnectAsFollower"]
210+
):
211+
if core._hasShutdownBeenTriggered:
212+
log.info("NVDA is shutting down, skipping remote disconnect confirmation dialog.")
213+
else:
214+
confirmation_buttons = (
215+
DefaultButton.YES,
216+
DefaultButton.NO.value._replace(defaultFocus=True, fallbackAction=True),
217+
)
218+
219+
dialog = MessageDialog(
220+
parent=None,
221+
# Translators: Title of the Remote Access disconnection confirmation dialog.
222+
title=pgettext("remote", "Confirm Disconnection"),
223+
message=pgettext(
224+
"remote",
225+
# Translators: Message shown when disconnecting from the remote computer.
226+
"Are you sure you want to disconnect from the Remote Access session?",
227+
),
228+
dialogType=DialogType.WARNING,
229+
buttons=confirmation_buttons,
230+
)
231+
232+
if dialog.ShowModal() != ReturnCode.YES:
233+
log.info("Remote disconnection cancelled by user.")
234+
return
235+
202236
log.info("Disconnecting from remote session")
203237
if self.localControlServer is not None:
204238
self.localControlServer.close()

source/_remoteClient/cues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _playCue(cueName: str) -> None:
6565

6666
# Show message if specified
6767
if message := CUES[cueName].get("message"):
68-
ui.message(message)
68+
ui.delayedMessage(message)
6969

7070

7171
def connected():

source/config/configSpec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@
363363
__many__ = string(default="")
364364
[[ui]]
365365
playSounds = boolean(default=True)
366+
confirmDisconnectAsFollower = boolean(default=True)
366367
"""
367368

368369
#: The configuration specification

source/globalCommands.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4911,8 +4911,6 @@ def script_pushClipboard(self, gesture: "inputCore.InputGesture"):
49114911
@gui.blockAction.when(gui.blockAction.Context.REMOTE_ACCESS_DISABLED)
49124912
def script_copyRemoteLink(self, gesture: "inputCore.InputGesture"):
49134913
_remoteClient._remoteClient.copyLink()
4914-
# Translators: A message indicating that a link has been copied to the clipboard.
4915-
ui.message(_("Copied link"))
49164914

49174915
@script(
49184916
category=SCRCAT_REMOTE,

source/gui/blockAction.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,8 @@
1111
from functools import wraps
1212
import globalVars
1313
from typing import Any
14-
from speech.priorities import SpeechPriority
1514
import ui
1615
from utils.security import isLockScreenModeActive, isRunningOnSecureDesktop
17-
import core
18-
19-
_DELAY_BEFORE_MESSAGE_MS = 1
20-
"""Duration in milliseconds for which to delay announcing that an action has been blocked, so that any UI changes don't interrupt it.
21-
1ms is a magic number. It can be increased if it is found to be too short, but it should be kept to a minimum.
22-
"""
2316

2417

2518
def _isModalMessageBoxActive() -> bool:
@@ -114,12 +107,7 @@ def funcWrapper(*args, **kwargs):
114107
if context.callback is not None:
115108
context.callback()
116109
# We need to delay this message so that, if a UI change is triggered by the callback, the UI change doesn't interrupt it.
117-
core.callLater(
118-
_DELAY_BEFORE_MESSAGE_MS,
119-
ui.message,
120-
context.translatedMessage,
121-
SpeechPriority.NOW,
122-
)
110+
ui.delayedMessage(context.translatedMessage)
123111
return
124112
return func(*args, **kwargs)
125113

0 commit comments

Comments
 (0)