Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 110 additions & 12 deletions projectDocs/dev/developerGuide/developerGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -986,10 +986,10 @@ class GlobalPlugin(globalPluginHandler.GlobalPlugin):

## Packaging Code as NVDA Add-ons {#Addons}

Add-ons make it easy for users to share and install plugins, drivers, speech symbol dictionaries and braille translation tables.
Add-ons make it easy for users to share and install plugins, drivers, speech pronunciation/symbol dictionaries and braille translation tables.
They can be packaged in to a single NVDA add-on package, which the user can then install into a copy of NVDA via the Add-on Store found under Tools in the NVDA menu.
An add-on package is simply a standard zip archive with the file extension of "`nvda-addon`".
It can contain a manifest file, install/uninstall code and directories containing plugins, drivers, speech symbol dictionaries and braille translation tables.
It can contain a manifest file, install/uninstall code and directories containing plugins, drivers, speech dictionaries, symbol dictionaries and braille translation tables.

### Non-ASCII File Names in Zip Archives {#nonASCIIFileNamesInZip}

Expand Down Expand Up @@ -1057,8 +1057,8 @@ The lastTestedNVDAVersion field in particular is used to ensure that users can b
It allows the add-on author to make an assurance that the add-on will not cause instability, or break the users system.
When this is not provided, or is less than the current version of NVDA (ignoring minor point updates e.g. 2018.3.1) then the user will be warned not to install the add-on.

The manifest can also specify information regarding any additional speech symbol dictionaries or braille translation tables provided by the add-on.
Please refer to the [speech symbol dictionaries](#AddonSymbolDictionaries) and [braille translation tables](#BrailleTables) sections.
The manifest can also specify information regarding any additional speech dictionaries, symbol or braille translation tables provided by the add-on.
Please refer to the [speech dictionaries](#AddonSpeechDictionaries), [symbol dictionaries](#AddonSymbolDictionaries) and [braille translation tables](#BrailleTables) sections.

#### An Example Manifest File {#manifestExample}

Expand All @@ -1082,7 +1082,8 @@ The following plugins and drivers can be included in an add-on:
* Braille display drivers: Place them in a `brailleDisplayDrivers` directory in the archive.
* Global plugins: Place them in a `globalPlugins` directory in the archive.
* Synthesizer drivers: Place them in a `synthDrivers` directory in the archive.
* [Speech symbol dictionaries](#AddonSymbolDictionaries): Place them in the directory for one or more [locales](#localizingAddons) with a file name of `symbols-<name>.dic`, e.g. `locale\en\symbols-greek.dic`.
* [Speech dictionaries](#AddonSpeechDictionaries): Place them in the `speechDicts` directory with a file name with the `.dic` extension, e.g. `speechDicts\pronunciation.dic`.
* [Symbol dictionaries](#AddonSymbolDictionaries): Place them in the directory for one or more [locales](#localizingAddons) with a file name of `symbols-<name>.dic`, e.g. `locale\en\symbols-greek.dic`.
* [Braille translation tables](#BrailleTables): Place them in a `brailleTables` directory in the archive.

### Optional install / Uninstall code {#installUninstallCode}
Expand Down Expand Up @@ -1125,18 +1126,18 @@ To allow plugins in your add-on to access gettext message information via calls
This function cannot be called in modules that do not belong to an add-on, e.g. in a scratchpad subdirectory.
For more information about gettext and NVDA translation in general, please read the [Translating NVDA page](https://github.com/nvaccess/nvda/blob/master/projectDocs/translating/readme.md)

#### Speech symbol dictionaries {#AddonSymbolDictionaries}
#### Symbol dictionaries {#AddonSymbolDictionaries}

You can provide custom speech symbol dictionaries in add-ons to improve symbol pronunciation.
The process to create custom speech symbol dictionaries is very similar to that of the [translation process of existing symbols](#symbolPronunciation).
Note that [complex symbols](#complexSymbols) are not supported.

Custom dictionaries must be placed in a language directory and have a filename in the form `symbols-<name>.dic`, where `<name>` is the name that has to be provided in the add-ons manifest.
Custom symbol dictionaries must be placed in a language directory and have a filename in the form `symbols-<name>.dic`, where `<name>` is the name that has to be provided in the add-ons manifest.
All locales implicitly inherit the symbol information for English, though any of this information can be overridden for specific locales.

When adding a dictionary not marked as mandatory, some information must be provided such as its display name, since it should be shown in the speech category of the settings dialog.
A dictionary can also be marked mandatory, in which case it is always enabled with the add-on.
When an add-on ships with dictionaries, this information is included in its manifest in the optional `symbolDictionaries` section.
When adding a symbol dictionary not marked as mandatory, some information must be provided such as its display name, since it should be shown in the speech category of the settings dialog.
A symbol dictionary can also be marked mandatory, in which case it is always enabled with the add-on.
When an add-on ships with symbol dictionaries, this information is included in its manifest in the optional `symbolDictionaries` section.
For example:

```ini
Expand All @@ -1150,14 +1151,14 @@ displayName = Biblical Hebrew
mandatory = true
```

In the above example, `greek` is a dictionary that is optional and will be listed in the speech category of NVDA's settings dialog under the "Extra dictionaries for character and symbol processing" setting.
In the above example, `greek` is a symbol dictionary that is optional and will be listed in the speech category of NVDA's settings dialog under the "Extra dictionaries for character and symbol processing" setting.
Its file will be stored as `locale\en\symbols-greek.dic`, whereas French translations of the symbols are stored in `locale\fr\symbols-greek.dic`.
When using NVDA in French, symbols that aren't defined in the French dictionary inherit the symbol information for English.

Also in the example, the `hebrew` dictionary is marked mandatory and will therefore always be enabled as long as the add-on is active.
Its file will be stored as `locale\en\symbols-hebrew.dic`, whereas French translations of the symbols are stored in `locale\fr\symbols-hebrew.dic`.

Note that for the display name of the dictionary to be translated, an entry should be added to a [locale manifest](#localeManifest).
Note that for the display name of the symbol dictionary to be translated, an entry should be added to a [locale manifest](#localeManifest).
For example, add the following to `locale\fr\manifest.ini`:

```ini
Expand All @@ -1166,6 +1167,103 @@ For example, add the following to `locale\fr\manifest.ini`:
displayName = Hébreu Biblique
```

### Speech dictionaries {#AddonSpeechDictionaries}

You can provide custom speech dictionaries in add-ons to improve pronunciation of words that are usually pronounced incorrectly by speech synthesizers.
Custom dictionaries must be placed in the `speechDicts` directory and have a filename with the `.dic` extension.
For example, when your dictionary is named `<name>.dic`, `<name>` is the name that has to be provided in the add-ons manifest.

When adding a speech dictionary not marked as mandatory, some information must be provided such as its display name, since it should be shown in the speech category of the settings dialog.
A speech dictionary can also be marked mandatory, in which case it is always enabled with the add-on.
When an add-on ships with speech dictionaries, this information is included in its manifest in the optional `speechDictionaries` section.
For example:

```ini
[speechDictionaries]
[[pronunciation]]
displayName = Dodgy Dictionary
mandatory = false
```

In the above example, `pronunciation` is a dictionary that is optional and will be listed in the speech category of NVDA's settings dialog under the "Speech Dictionaries" setting.
Its file will be stored as `speechDicts\pronunciation.dic`.
When you mark the dictionary as mandatory, it will be always enabled as long as the add-on is active.

Note that for the display name of the dictionary to be translated, an entry should be added to a [locale manifest](#localeManifest).
For example, add the following to `locale\fr\manifest.ini`:

```ini
[speechDictionaries]
[[pronunciation]]
displayName = Dictionnaire douteux
```

Unlike symbol dictionaries, speech dictionaries are currently locale-agnostic.
Therefore, an active dictionary is always active, regardless of the current language.

#### Creating speech dictionaries {#AddonCreatingSpeechDictionaries}

A speech dictionary file contains dictionary rules, one per line.
Each dictionary rule consists of four tab-separated fields on a single line:

```
<pattern> <replacement> <caseSensitive> <type>
```

The fields are:

1. `pattern`: The text pattern to match.
Hash characters (`#`) must be escaped as `\#`.
2. `replacement`: The text to replace the matched pattern with.
Hash characters (`#`) must be escaped as `\#`.
3. `caseSensitive`: A numeric flag indicating case sensitivity:
* `0`: Case insensitive matching
* `1`: Case sensitive matching
4. `type`: A number indicating the type of pattern matching to use:
* `0`: Anywhere - Pattern can match anywhere in the text (literal string)
* `1`: Regular expression - Pattern is treated as a Python regular expression
* `2`: Whole word - Pattern must match a complete word with word boundaries on both sides
* `3`: Part of word - Pattern must be preceded or followed by a word character (letter, digit, underscore)
* `4`: Start of word - Pattern must have a word boundary at the start and a word character at the end
* `5`: End of word - Pattern must have a word character at the start and a word boundary at the end
* `6`: Unix shell-style wildcards - Pattern uses Unix shell wildcards (`*`, `?`, `[]`, etc.)

Comments can be added before entries to provide descriptions.
A comment is preceded by a `#` (hash sign) and applies to the next entry line that appears after it.

##### Examples

```
# Expand NVDA acronym
NVDA NonVisual Desktop Access 1 2
```

This means that the word "NVDA" (case sensitive, whole word) should be spoken as "NonVisual Desktop Access".

```
# Convert percentages to spoken format
(\d+)% \1 percent 0 1
```

This uses a regular expression to match numbers followed by a percent sign and replaces them with the number followed by the word "percent".
For example, "50%" becomes "50 percent".

```
# Change "LOL" to full phrase
LOL laughing out loud 0 2
```

This means that the word "LOL" (case insensitive, whole word) should be spoken as "laughing out loud".

```
# Match any .txt file using wildcards
*.txt text file 0 6
```

This uses Unix shell-style wildcards to match any string ending in ".txt" and replaces it with "text file".

For more information on speech dictionaries, see the NVDA user guide section on speech.

### Add-on Documentation {#AddonDoc}

Documentation for an add-on should be placed in the `doc` directory in the archive.
Expand Down
11 changes: 11 additions & 0 deletions source/addonHandler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,13 @@ class AddonManifest(ConfigObj):
displayName = string()
mandatory = boolean(default=false)

# Speech Dictionaries
[speechDictionaries]
# The key is the speech dictionary file name (not the full path)
[[__many__]]
displayName = string()
mandatory = boolean(default=false)

# NOTE: apiVersion:
# EG: 2019.1.0 or 0.0.0
# Must have 3 integers separated by dots.
Expand Down Expand Up @@ -1133,6 +1140,10 @@ def __init__(self, input: IO[bytes], translatedInput: IO[bytes] | None = None):
value = dictConfig.get("displayName")
if value:
self["symbolDictionaries"][fileName]["displayName"] = value
for fileName, dictConfig in self._translatedConfig.get("speechDictionaries", {}).items():
value = dictConfig.get("displayName")
if value:
self["speechDictionaries"][fileName]["displayName"] = value

@property
def errors(self):
Expand Down
1 change: 1 addition & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
unicodeNormalization = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled")
reportNormalizedForCharacterNavigation = boolean(default=true)
symbolDictionaries = string_list(default=list("cldr"))
speechDictionaries = string_list(default=list("default", "voice"))
beepSpeechModePitch = integer(default=10000,min=50,max=11025)
autoLanguageSwitching = boolean(default=true)
autoDialectSwitching = boolean(default=false)
Expand Down
26 changes: 25 additions & 1 deletion source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import queueHandler
import requests
import speech
import speechDictHandler
import systemUtils
import vision
import vision.providerBase
Expand Down Expand Up @@ -1768,6 +1769,8 @@ def makeSettings(self, settingsSizer):

self._appendDelayedCharacterDescriptions(settingsSizerHelper)

self._appendSpeechDictionariesList(settingsSizerHelper)

minPitchChange = int(
config.conf.getConfigValidation(
("speech", self.driver.name, "capPitchChange"),
Expand Down Expand Up @@ -1837,7 +1840,7 @@ def _appendSymbolDictionariesList(self, settingsSizerHelper: guiHelper.BoxSizerH
d for d in characterProcessing.listAvailableSymbolDictionaryDefinitions() if d.userVisible
]
self.symbolDictionariesList: nvdaControls.CustomCheckListBox = settingsSizerHelper.addLabeledControl(
# Translators: Label of the list where user can enable or disable symbol dictionaires.
# Translators: Label of the list where user can enable or disable symbol dictionaries.
_("E&xtra dictionaries for character and symbol processing:"),
nvdaControls.CustomCheckListBox,
choices=[d.displayName for d in self._availableSymbolDictionaries],
Expand All @@ -1848,6 +1851,22 @@ def _appendSymbolDictionariesList(self, settingsSizerHelper: guiHelper.BoxSizerH
]
self.symbolDictionariesList.Select(0)

def _appendSpeechDictionariesList(self, settingsSizerHelper: guiHelper.BoxSizerHelper) -> None:
self._availableSpeechDictionaries = [
d for d in speechDictHandler.listAvailableSpeechDictDefinitions(forDisplay=True) if d.userVisible
]
self.speechDictionariesList: nvdaControls.CustomCheckListBox = settingsSizerHelper.addLabeledControl(
# Translators: Label of the list where user can enable or disable speech dictionaries.
_("Sp&eech dictionaries:"),
nvdaControls.CustomCheckListBox,
choices=[d.displayName for d in self._availableSpeechDictionaries],
)
self.bindHelpEvent("SpeechDictionaries", self.speechDictionariesList)
self.speechDictionariesList.CheckedItems = [
i for i, d in enumerate(self._availableSpeechDictionaries) if d.enabled
]
self.speechDictionariesList.Select(0)

def _appendSpeechModesList(self, settingsSizerHelper: guiHelper.BoxSizerHelper) -> None:
self._allSpeechModes = list(speech.SpeechMode)
self.speechModesList: nvdaControls.CustomCheckListBox = settingsSizerHelper.addLabeledControl(
Expand Down Expand Up @@ -1905,6 +1924,11 @@ def onSave(self):
if set(currentSymbolDictionaries) != set(newSymbolDictionaries):
# Either included or excluded symbol dictionaries, so clear the cache.
characterProcessing.clearSpeechSymbols()
config.conf["speech"]["speechDictionaries"] = [
d.name
for i, d in enumerate(self._availableSpeechDictionaries)
if i in self.speechDictionariesList.CheckedItems
]
delayedDescriptions = self.delayedCharacterDescriptionsCheckBox.IsChecked()
config.conf["speech"]["delayedCharacterDescriptions"] = delayedDescriptions
config.conf["speech"][self.driver.name]["capPitchChange"] = self.capPitchChangeEdit.Value
Expand Down
19 changes: 9 additions & 10 deletions source/gui/speechDict.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,30 +325,29 @@ def onRemoveAll(self, evt):

class DefaultDictionaryDialog(DictionaryDialog):
def __init__(self, parent):
Copy link
Member

Choose a reason for hiding this comment

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

could you add the types for parent to these

definition = speechDictHandler.definitions._getDictionaryDefinition(DictionaryType.DEFAULT)
super().__init__(
parent,
# Translators: Title for default speech dictionary dialog.
title=_("Default dictionary"),
speechDict=speechDictHandler.dictionaries[DictionaryType.DEFAULT],
title=definition.displayName,
speechDict=definition.dictionary,
)


class VoiceDictionaryDialog(DictionaryDialog):
def __init__(self, parent):
definition = speechDictHandler.definitions._getDictionaryDefinition(DictionaryType.VOICE)
super().__init__(
parent,
# Translators: Title for voice dictionary for the current voice such as current eSpeak variant.
title=_("Voice dictionary (%s)") % speechDictHandler.dictionaries[DictionaryType.VOICE].fileName,
speechDict=speechDictHandler.dictionaries[DictionaryType.VOICE],
title=definition.displayName,
speechDict=definition.dictionary,
)


class TemporaryDictionaryDialog(DictionaryDialog):
def __init__(self, parent):
definition = speechDictHandler.definitions._getDictionaryDefinition(DictionaryType.TEMP)
super().__init__(
parent,
# Translators: Title for temporary speech dictionary dialog (the voice dictionary that is active as long
# as NvDA is running).
title=_("Temporary dictionary"),
speechDict=speechDictHandler.dictionaries[DictionaryType.TEMP],
title=definition.displayName,
speechDict=definition.dictionary,
)
Loading
Loading