Skip to content

Commit 9d7ed9c

Browse files
committed
Add some missing translator comments and check for missing translator comments in future.
1. Add a few missing translator comments inspired by reports from translators. 2. Add some code (`checkPot`) to check for missing translator comments in the translation template (pot). For now, we have a set of expected failures, since fixing all existing 87 messages that don't have translator comments is going to take some work. However, we want to fail for any new messages that get introduced without comments. 3. Run `checkPot` as part of `scons tests`. This means tests (and thus builds) will fail so we learn about these problems early. 4. `checkPot` can also be run alone with `scons checkPot`. 5. Now that `scons tests` runs `checkPot` (which depends on `pot`), don't explicitly run `scons pot` on AppVeyor.
1 parent 890528b commit 9d7ed9c

7 files changed

Lines changed: 253 additions & 13 deletions

File tree

‎appveyor.yml‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,8 @@ build_script:
7575
- 'echo scons args: %sconsArgs%'
7676
- py scons.py source %sconsArgs%
7777
# We don't need launcher to run tests, so run the tests before launcher.
78-
# We also build the translation template (pot) just to ensure it succeeds.
79-
- py scons.py tests pot %sconsArgs%
80-
# We don't actually need the pot as a build artifact though.
78+
- py scons.py tests %sconsArgs%
79+
# The pot gets built by tests, but we don't actually need it as a build artifact.
8180
- del output\*.pot
8281
- 'echo scons output targets: %sconsOutTargets%'
8382
- py scons.py %sconsOutTargets% %sconsArgs%

‎readme.md‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,20 +208,26 @@ scons launcher version=test1
208208
## Running Automated Tests
209209
If you make a change to the NVDA code, you should run NVDA's automated tests.
210210
These tests help to ensure that code changes do not unintentionally break functionality that was previously working.
211-
Currently, NVDA has only one kind of automated testing: unit tests.
211+
Currently, NVDA has two kinds of automated testing: unit tests and translatable string checks.
212212

213-
To run the unit tests, first change directory to the root of the NVDA source distribution as above.
213+
To run the tests, first change directory to the root of the NVDA source distribution as above.
214214
Then, run:
215215

216216
```
217217
scons tests
218218
```
219219

220-
To run only specific tests, specify them using the `unitTests` variable on the command line.
220+
To run only specific unit tests, specify them using the `unitTests` variable on the command line.
221221
The tests should be provided as a comma separated list.
222222
Each test should be specified as a Python module, class or method relative to the `tests\unit` directory.
223223
For example, to run only methods in the `TestMove` and `TestSelection` classes in the file `tests\unit\test_cursorManager.py` file, run this command:
224224

225225
```
226226
scons tests unitTests=test_cursorManager.TestMove,test_cursorManager.TestSelection
227227
```
228+
229+
To run only the translatable string checks (which check that all translatable strings have translator comments), run:
230+
231+
```
232+
scons checkPot
233+
```

‎sconstruct‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,4 @@ env.Alias("symbolsArchive", symbolsArchive)
399399

400400
env.Default(dist)
401401

402-
if "tests" in COMMAND_LINE_TARGETS:
403-
exports=["env"])
404-
env.Depends(target, sourceDir)
405-
env.AlwaysBuild(target)
406-
tests = env.Alias("tests", target)
402+
env.SConscript("tests/sconscript", exports=["env", "sourceDir", "pot"])

‎source/NVDAObjects/window/winword.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ def label(self):
363363
author=self.collectionItem.author
364364
date=self.collectionItem.date
365365
text=self.collectionItem.range.text
366+
# Translators: The label shown for a comment in the NVDA Elements List dialog in Microsoft Word.
367+
# {text}, {author} and {date} will be replaced by the corresponding details about the comment.
366368
return _(u"comment: {text} by {author} on {date}").format(author=author,text=text,date=date)
367369

368370
def rangeFromCollectionItem(self,item):
@@ -380,6 +382,10 @@ def label(self):
380382
date=self.collectionItem.date
381383
description=self.collectionItem.formatDescription or ""
382384
text=(self.collectionItem.range.text or "")[:100]
385+
# Translators: The label shown for an editor revision (tracked change) in the NVDA Elements List dialog in Microsoft Word.
386+
# {revisionType} will be replaced with the type of revision; e.g. insertion, deletion or property.
387+
# {description} will be replaced with a description of the formatting changes, if any.
388+
# {text}, {author} and {date} will be replaced by the corresponding details about the revision.
383389
return _(u"{revisionType} {description}: {text} by {author} on {date}").format(revisionType=revisionType,author=author,text=text,date=date,description=description)
384390

385391
class WordDocumentSpellingErrorQuickNavItem(WordDocumentCollectionQuickNavItem):
@@ -390,7 +396,8 @@ def rangeFromCollectionItem(self,item):
390396
@property
391397
def label(self):
392398
text=self.collectionItem.text
393-
# Translators: the label for a speling error shown in NVDA's Elements List dialog for Microsoft Word
399+
# Translators: The label shown for a spelling error in the NVDA Elements List dialog in Microsoft Word.
400+
# {text} will be replaced with the text of the spelling error.
394401
return _(u"spelling: {text}").format(text=text)
395402

396403
class WinWordCollectionQuicknavIterator(object):

‎tests/checkPot.py‎

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#tests/checkPot.py
2+
#A part of NonVisual Desktop Access (NVDA)
3+
#This file is covered by the GNU General Public License.
4+
#See the file COPYING for more details.
5+
#Copyright (C) 2017 NV Access Limited
6+
7+
"""Check a translation template (pot) for strings without translator comments.
8+
"""
9+
10+
import sys
11+
12+
# Existing messages that we know don't have translator comments yet.
13+
# Ideally, all of these should get translator comments,
14+
# but this is not realistic right now.
15+
# A message should be removed from here once a translator comment is added for it.
16+
EXPECTED_MESSAGES_WITHOUT_COMMENTS = {
17+
'Focus mode',
18+
'%s landmark',
19+
'border',
20+
'filler',
21+
'line',
22+
'data item',
23+
'header item',
24+
'calendar',
25+
'video',
26+
'audio',
27+
'modal',
28+
'iconified',
29+
'editable',
30+
'checkable',
31+
'draggable',
32+
'dragging',
33+
'sorted',
34+
'sorted ascending',
35+
'sorted descending',
36+
'gesture map File Error',
37+
'text \\"%s\\" not found',
38+
'Find Error',
39+
'Selected %s',
40+
'on',
41+
'Type help(object) to get help about object.',
42+
'Type exit() to exit the console',
43+
'NVDA Python Console',
44+
'Emulates pressing %s on the system keyboard',
45+
'continuous section break',
46+
'new column section break',
47+
'new page section break',
48+
'even pages section break',
49+
'odd pages section break',
50+
'column break',
51+
'background pattern {pattern}',
52+
'NVDA Speech Viewer',
53+
'text mode',
54+
'object mode',
55+
'NonVisual Desktop Access',
56+
'A free and open source screen reader for Microsoft Windows',
57+
'Copyright (C) {years} NVDA Contributors',
58+
'Display',
59+
'Reports and moves the review cursor to a recent message',
60+
'left',
61+
'right',
62+
'Show the Handy Tech driver configuration window.',
63+
'Recognition failed',
64+
'General settings',
65+
'Change the synthesizer to be used',
66+
'Choose the voice, rate, pitch and volume to use',
67+
'Change reporting of mouse shape and object under mouse',
68+
'Configure how and when the review cursor moves',
69+
'Change reporting of objects',
70+
'Change virtual buffers specific settings',
71+
'Change settings of document properties',
72+
'NVDA &web site',
73+
'Reset all settings to saved state',
74+
'Reset all settings to default state',
75+
'Write the current configuration to nvda.ini',
76+
'E&xit',
77+
'Options',
78+
'Error renaming profile.',
79+
'Use this profile for:',
80+
'This change requires administrator privileges.',
81+
'Insufficient Privileges',
82+
'Synthesizer Error',
83+
'Dictionary Entry Error',
84+
'Could not load the %s display.',
85+
'Braille Display Error',
86+
'word',
87+
'Taskbar',
88+
'%s items',
89+
'invoke',
90+
'Desktop',
91+
'Input Message is {title}: {message}',
92+
'Input Message is {message}',
93+
'Series color: {colorName} ',
94+
'Comments',
95+
'Endnotes',
96+
'Even pages footer',
97+
'Even pages header',
98+
'First page footer',
99+
'First page header',
100+
'Footnotes',
101+
'Primary footer',
102+
'Primary header',
103+
'Text frame',
104+
}
105+
106+
def checkPot(fileName):
107+
"""Returns the number of errors.
108+
Also prints error messages and a summary to standard output.
109+
"""
110+
errors = 0
111+
expectedErrors = 0
112+
unexpectedSuccesses = 0
113+
with file(fileName, "rt") as pot:
114+
for line in pot:
115+
line = line.rstrip()
116+
if not line:
117+
# End of message.
118+
hasComment = False
119+
sourceLines = []
120+
context = ""
121+
continue
122+
if line == 'msgid ""':
123+
# Header.
124+
continue
125+
if line.startswith("#. Translators: "):
126+
# Example: "#. Translators: a message reported in the SetRowHeader script for Microsoft Word."
127+
hasComment = True
128+
continue
129+
if line.startswith("#: "):
130+
# Example: "#: NVDAObjects\window\winword.py:1322"
131+
# Strip the "#: " prefix (3 chars).
132+
sourceLines.append(line[3:])
133+
continue
134+
if line.startswith('msgctxt "'):
135+
context = getStringFromLine(line)
136+
continue
137+
if line.startswith("msgid "):
138+
# Get the message.
139+
if line == 'msgid ""':
140+
# Multi-line msgid.
141+
# Subsequent lines are just quoted strings which should be concatenated.
142+
msgid = ""
143+
for line in pot:
144+
if line.startswith("msgstr "):
145+
break
146+
msgid += getStringFromLine(line)
147+
else:
148+
# Single line msgid.
149+
# Example: msgid "Secure Desktop"
150+
msgid = getStringFromLine(line)
151+
if context:
152+
# The context must be considered as part of the message.
153+
message = "[{context}] {msgid}".format(context=context, msgid=msgid)
154+
else:
155+
message = msgid
156+
isExpectedError = message in EXPECTED_MESSAGES_WITHOUT_COMMENTS
157+
if not hasComment and isExpectedError:
158+
expectedErrors += 1
159+
continue
160+
if hasComment and isExpectedError:
161+
error = ("Message has translator comment, but one wasn't expected.\n"
162+
"This is good, but please remove from EXPECTED_MESSAGES_WITHOUT_COMMENTS in tests/checkPot.py")
163+
unexpectedSuccesses += 1
164+
elif not hasComment:
165+
errors += 1
166+
error = "Message has no translator comment."
167+
else:
168+
continue
169+
print("{error}\n"
170+
"Source lines: {lines}\n"
171+
"Message: {message}\n"
172+
.format(error=error, lines=" ".join(sourceLines), message=message))
173+
continue
174+
print("{errors} errors, {unexpectedSuccesses} unexpected successes, {expectedErrors} expected errors"
175+
.format(errors=errors, unexpectedSuccesses=unexpectedSuccesses, expectedErrors=expectedErrors))
176+
return errors + unexpectedSuccesses
177+
178+
def getStringFromLine(line):
179+
if line.startswith('"'):
180+
# The quoted string begins at the start of the line.
181+
quoted = line
182+
else:
183+
# The quoted string starts after a command.
184+
# Example: msgid "Secure Desktop"
185+
quoted = line.split(" ", 1)[1]
186+
# Strip the quotes.
187+
return quoted[1:-1]
188+
189+
if __name__ == "__main__":
190+
# Support command line usage for quick testing.
191+
fileName = sys.argv[1]
192+
print(checkPot(fileName))

‎tests/sconscript‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
###
2+
#This file is a part of the NVDA project.
3+
#URL: http://www.nvaccess.org/
4+
#Copyright 2017 NV Access Limited.
5+
#This program is free software: you can redistribute it and/or modify
6+
#it under the terms of the GNU General Public License version 2.0, as published by
7+
#the Free Software Foundation.
8+
#This program is distributed in the hope that it will be useful,
9+
#but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
#This license can be found at:
12+
#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
###
14+
15+
import checkPot
16+
17+
Import("env", "sourceDir", "pot")
18+
19+
unitTests = env.SConscript("unit/sconscript", exports=["env"])
20+
env.Depends(unitTests, sourceDir)
21+
env.AlwaysBuild(unitTests)
22+
23+
def checkPotAction(target, source, env):
24+
return checkPot.checkPot(source[0].abspath)
25+
checkPotTarget = env.Command("checkPot", pot, checkPotAction)
26+
env.Depends(checkPotTarget, pot)
27+
env.AlwaysBuild(checkPotTarget)
28+
env.Alias("checkPot", checkPotTarget)
29+
30+
# Determine the targets for scons tests.
31+
# If specific tests are explicitly specified, only run those.
32+
explicitUnitTests = env.get("unitTests")
33+
explicitCheckPot = "checkPot" in COMMAND_LINE_TARGETS
34+
explicit = explicitUnitTests or explicitCheckPot
35+
tests = []
36+
if not explicit or explicitUnitTests:
37+
tests.append(unitTests)
38+
if not explicit or explicitCheckPot:
39+
tests.append(checkPotTarget)
40+
env.Alias("tests", tests)

‎tests/unit/sconscript‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import sys
1717
Import("env")
1818

1919
cmd = [sys.executable, "-m", "unittest"]
20-
tests = env["unitTests"]
20+
tests = env.get("unitTests")
2121
if tests:
2222
# Run specific tests.
2323
tests = tests.split(",")

0 commit comments

Comments
 (0)