Merge pull request #679 from vkbo/norwegian_locale
Norwegian Localisation
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
# novelWriter Internationalisation
|
||||||
|
|
||||||
|
The `i18n` folder contains the internationalisation files for novelWriter.
|
||||||
|
There are two types of files. The `nw_XX.qm` files are for localisation of the
|
||||||
|
main GUI, and the `project_XX.json` files are JSON files with translation maps
|
||||||
|
for the novelWriter projects.
|
||||||
|
|
||||||
|
## GUI Localisation
|
||||||
|
|
||||||
|
The `.qm` files are not generated by default. They can be built with:
|
||||||
|
```bash
|
||||||
|
python3 setup.py qtlrelease
|
||||||
|
```
|
||||||
|
|
||||||
|
To add a new translation file, please add a new entry in the `novelWriter.pro`
|
||||||
|
file under the `TRANSLATIONS` variable. Please use the underscore syntax for
|
||||||
|
file names as the importer expects this format.
|
||||||
|
|
||||||
|
After adding the entry in the `.pro` file, run:
|
||||||
|
```bash
|
||||||
|
python3 setup.py qtlupdate
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build a new `nw_XX.ts` file for the language you just added. The file
|
||||||
|
can then be edited with the Qt 5 Linguist application. Please select "English"
|
||||||
|
and "United Kingdom" as the source langauge when prompted.
|
||||||
|
|
||||||
|
When you're done editing, you can build the `nw_XX.qm` file and test it in
|
||||||
|
novelWriter.
|
||||||
|
|
||||||
|
**Note:** This requires that you have the tools Qt 5 Linguist and `pylupdate5`
|
||||||
|
installed on your system.
|
||||||
|
|
||||||
|
## Project Localisation
|
||||||
|
|
||||||
|
Projects can have a different language setting than the GUI itself. The files
|
||||||
|
with format `project_XX.json` are simple translation maps for text that goes
|
||||||
|
into the exported documents.
|
||||||
|
|
||||||
|
The files are loaded based on the main GUI spell check language or the
|
||||||
|
project's spell check language. The project's spell check language takes
|
||||||
|
precedence over the GUI spell check language.
|
||||||
|
|
||||||
|
Since these are used mainly as replacement lookups, they are maintained as
|
||||||
|
plain JSON files. The main usage is to generate chapter headers as number
|
||||||
|
words.
|
||||||
|
|
||||||
|
There is no trick to using these files. Just copy the `project_en.json` file,
|
||||||
|
rename it, and edit it. Please use the underscore as separator if a language
|
||||||
|
needs to be localised to a specific country under a language. The importer will
|
||||||
|
for instance look for `en_GB` or `en_US` first, then use `en` if either is not
|
||||||
|
found. If there are no matching files, the project falls back to using English.
|
||||||
+4762
File diff suppressed because it is too large
Load Diff
+47
-36
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS><TS version="2.0" language="pt" sourcelanguage="en">
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="pt" sourcelanguage="en">
|
||||||
<context>
|
<context>
|
||||||
<name>Constant</name>
|
<name>Constant</name>
|
||||||
<message>
|
<message>
|
||||||
@@ -678,6 +679,11 @@
|
|||||||
<source>JSON + novelWriter Markdown</source>
|
<source>JSON + novelWriter Markdown</source>
|
||||||
<translation>JSON + Markdown do novelWriter</translation>
|
<translation>JSON + Markdown do novelWriter</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/build.py" line="260"/>
|
||||||
|
<source>Styling Options</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiBuildNovelDocView</name>
|
<name>GuiBuildNovelDocView</name>
|
||||||
@@ -966,16 +972,16 @@
|
|||||||
<source>Element selected in the project tree must be a folder.</source>
|
<source>Element selected in the project tree must be a folder.</source>
|
||||||
<translation>O elemento selecionado na árvore do projeto deve ser um diretório.</translation>
|
<translation>O elemento selecionado na árvore do projeto deve ser um diretório.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../nw/gui/docmerge.py" line="69"/>
|
|
||||||
<source>Ok</source>
|
|
||||||
<translation>OK</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/gui/docmerge.py" line="70"/>
|
<location filename="../nw/gui/docmerge.py" line="70"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Cancelar</translation>
|
<translation>Cancelar</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/docmerge.py" line="69"/>
|
||||||
|
<source>OK</source>
|
||||||
|
<translation>OK</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiDocSplit</name>
|
<name>GuiDocSplit</name>
|
||||||
@@ -1049,16 +1055,16 @@
|
|||||||
<source>The document will be split into {0} file(s) in a new folder. The original document will remain intact.</source>
|
<source>The document will be split into {0} file(s) in a new folder. The original document will remain intact.</source>
|
||||||
<translation>O documento será dividio em {0} arquivo(s) em um novo diretório. O documento original será mantido intacto.</translation>
|
<translation>O documento será dividio em {0} arquivo(s) em um novo diretório. O documento original será mantido intacto.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../nw/gui/docsplit.py" line="85"/>
|
|
||||||
<source>Ok</source>
|
|
||||||
<translation>OK</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/gui/docsplit.py" line="86"/>
|
<location filename="../nw/gui/docsplit.py" line="86"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Cancelar</translation>
|
<translation>Cancelar</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/docsplit.py" line="85"/>
|
||||||
|
<source>OK</source>
|
||||||
|
<translation>OK</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiDocViewFooter</name>
|
<name>GuiDocViewFooter</name>
|
||||||
@@ -1232,16 +1238,16 @@
|
|||||||
<source>Layout</source>
|
<source>Layout</source>
|
||||||
<translation>Leiaute</translation>
|
<translation>Leiaute</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../nw/gui/itemeditor.py" line="120"/>
|
|
||||||
<source>Ok</source>
|
|
||||||
<translation>OK</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/gui/itemeditor.py" line="121"/>
|
<location filename="../nw/gui/itemeditor.py" line="121"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Cancelar</translation>
|
<translation>Cancelar</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/itemeditor.py" line="120"/>
|
||||||
|
<source>OK</source>
|
||||||
|
<translation>OK</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiMain</name>
|
<name>GuiMain</name>
|
||||||
@@ -1450,6 +1456,11 @@
|
|||||||
<source>All files ({0})</source>
|
<source>All files ({0})</source>
|
||||||
<translation>Todos os arquivos ({0})</translation>
|
<translation>Todos os arquivos ({0})</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/guimain.py" line="293"/>
|
||||||
|
<source>novelWriter is ready ...</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiMainMenu</name>
|
<name>GuiMainMenu</name>
|
||||||
@@ -2733,16 +2744,16 @@
|
|||||||
<source>Automation</source>
|
<source>Automation</source>
|
||||||
<translation>Automação</translation>
|
<translation>Automação</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../nw/gui/preferences.py" line="73"/>
|
|
||||||
<source>Ok</source>
|
|
||||||
<translation>OK</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/gui/preferences.py" line="74"/>
|
<location filename="../nw/gui/preferences.py" line="74"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Cancelar</translation>
|
<translation>Cancelar</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/preferences.py" line="73"/>
|
||||||
|
<source>OK</source>
|
||||||
|
<translation>OK</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiPreferencesAutomation</name>
|
<name>GuiPreferencesAutomation</name>
|
||||||
@@ -3740,16 +3751,16 @@
|
|||||||
<source>Auto-Replace</source>
|
<source>Auto-Replace</source>
|
||||||
<translation>Substituição Automática</translation>
|
<translation>Substituição Automática</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../nw/gui/projsettings.py" line="80"/>
|
|
||||||
<source>Ok</source>
|
|
||||||
<translation>OK</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/gui/projsettings.py" line="81"/>
|
<location filename="../nw/gui/projsettings.py" line="81"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Cancelar</translation>
|
<translation>Cancelar</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/projsettings.py" line="80"/>
|
||||||
|
<source>OK</source>
|
||||||
|
<translation>OK</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>GuiProjectTree</name>
|
<name>GuiProjectTree</name>
|
||||||
@@ -4184,11 +4195,6 @@
|
|||||||
<source>Trash</source>
|
<source>Trash</source>
|
||||||
<translation>Lixeira</translation>
|
<translation>Lixeira</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../nw/core/project.py" line="206"/>
|
|
||||||
<source>Chapter %ch%: %title%</source>
|
|
||||||
<translation>Capítulo %ch%: %title%</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/core/project.py" line="219"/>
|
<location filename="../nw/core/project.py" line="219"/>
|
||||||
<source>New</source>
|
<source>New</source>
|
||||||
@@ -4489,6 +4495,11 @@
|
|||||||
<source>and</source>
|
<source>and</source>
|
||||||
<translation>e</translation>
|
<translation>e</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/core/project.py" line="206"/>
|
||||||
|
<source>Chapter</source>
|
||||||
|
<translation>Capítulo</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ProjWizardCustomPage</name>
|
<name>ProjWizardCustomPage</name>
|
||||||
@@ -4662,16 +4673,16 @@
|
|||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>QuotesDialog</name>
|
<name>QuotesDialog</name>
|
||||||
<message>
|
|
||||||
<location filename="../nw/gui/custom.py" line="503"/>
|
|
||||||
<source>Ok</source>
|
|
||||||
<translation>OK</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../nw/gui/custom.py" line="504"/>
|
<location filename="../nw/gui/custom.py" line="504"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Cancelar</translation>
|
<translation>Cancelar</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../nw/gui/custom.py" line="503"/>
|
||||||
|
<source>OK</source>
|
||||||
|
<translation>OK</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Tokenizer</name>
|
<name>Tokenizer</name>
|
||||||
|
|||||||
@@ -102,4 +102,4 @@
|
|||||||
"97": "Ninety-Seven",
|
"97": "Ninety-Seven",
|
||||||
"98": "Ninety-Eight",
|
"98": "Ninety-Eight",
|
||||||
"99": "Ninety-Nine"
|
"99": "Ninety-Nine"
|
||||||
}
|
}
|
||||||
@@ -102,4 +102,4 @@
|
|||||||
"97": "nittisju",
|
"97": "nittisju",
|
||||||
"98": "nittiåtte",
|
"98": "nittiåtte",
|
||||||
"99": "nittini"
|
"99": "nittini"
|
||||||
}
|
}
|
||||||
@@ -102,4 +102,4 @@
|
|||||||
"97": "nittisju",
|
"97": "nittisju",
|
||||||
"98": "nittiåtte",
|
"98": "nittiåtte",
|
||||||
"99": "nittini"
|
"99": "nittini"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Synopsis": "Sinopse",
|
||||||
|
"Comment": "Comentários",
|
||||||
|
"Notes": "Notas"
|
||||||
|
}
|
||||||
+3
-1
@@ -26,8 +26,10 @@ SOURCES += \
|
|||||||
nw/gui/theme.py \
|
nw/gui/theme.py \
|
||||||
nw/gui/wordlist.py \
|
nw/gui/wordlist.py \
|
||||||
nw/gui/writingstats.py \
|
nw/gui/writingstats.py \
|
||||||
|
nw/common.py \
|
||||||
nw/error.py \
|
nw/error.py \
|
||||||
nw/guimain.py
|
nw/guimain.py
|
||||||
|
|
||||||
TRANSLATIONS += \
|
TRANSLATIONS += \
|
||||||
i18n/nw_pt.ts
|
i18n/nw_pt.ts \
|
||||||
|
i18n/nw_nb_NO.ts
|
||||||
|
|||||||
+66
-14
@@ -29,6 +29,7 @@ import logging
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtWidgets import qApp
|
from PyQt5.QtWidgets import qApp
|
||||||
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwConst, nwUnicode, nwItemClass, nwItemType, nwItemLayout
|
nwConst, nwUnicode, nwItemClass, nwItemType, nwItemLayout
|
||||||
@@ -267,33 +268,61 @@ def fuzzyTime(secDiff):
|
|||||||
"""Converts a time difference in seconds into a fuzzy time string.
|
"""Converts a time difference in seconds into a fuzzy time string.
|
||||||
"""
|
"""
|
||||||
if secDiff < 0:
|
if secDiff < 0:
|
||||||
return "in the future"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "in the future"
|
||||||
|
)
|
||||||
elif secDiff < 30:
|
elif secDiff < 30:
|
||||||
return "just now"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "just now"
|
||||||
|
)
|
||||||
elif secDiff < 90:
|
elif secDiff < 90:
|
||||||
return "a minute ago"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "a minute ago"
|
||||||
|
)
|
||||||
elif secDiff < 3300: # 55 minutes
|
elif secDiff < 3300: # 55 minutes
|
||||||
return "%d minutes ago" % int(round(secDiff/60))
|
return QCoreApplication.translate(
|
||||||
|
"Common", "{0} minutes ago"
|
||||||
|
).format(int(round(secDiff/60)))
|
||||||
elif secDiff < 5400: # 90 minutes
|
elif secDiff < 5400: # 90 minutes
|
||||||
return "an hour ago"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "an hour ago"
|
||||||
|
)
|
||||||
elif secDiff < 84600: # 23.5 hours
|
elif secDiff < 84600: # 23.5 hours
|
||||||
return "%d hours ago" % int(round(secDiff/3600))
|
return QCoreApplication.translate(
|
||||||
|
"Common", "{0} hours ago"
|
||||||
|
).format(int(round(secDiff/3600)))
|
||||||
elif secDiff < 129600: # 1.5 days
|
elif secDiff < 129600: # 1.5 days
|
||||||
return "a day ago"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "a day ago"
|
||||||
|
)
|
||||||
elif secDiff < 561600: # 6.5 days
|
elif secDiff < 561600: # 6.5 days
|
||||||
return "%d days ago" % int(round(secDiff/86400))
|
return QCoreApplication.translate(
|
||||||
|
"Common", "{0} days ago"
|
||||||
|
).format(int(round(secDiff/86400)))
|
||||||
elif secDiff < 907200: # 10.5 days
|
elif secDiff < 907200: # 10.5 days
|
||||||
return "a week ago"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "a week ago"
|
||||||
|
)
|
||||||
elif secDiff < 2419200: # 28 days
|
elif secDiff < 2419200: # 28 days
|
||||||
return "%d weeks ago" % int(round(secDiff/604800))
|
return QCoreApplication.translate(
|
||||||
|
"Common", "{0} weeks ago"
|
||||||
|
).format(int(round(secDiff/604800)))
|
||||||
elif secDiff < 3888000: # 45 days
|
elif secDiff < 3888000: # 45 days
|
||||||
return "a month ago"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "a month ago"
|
||||||
|
)
|
||||||
elif secDiff < 29808000: # 345 days
|
elif secDiff < 29808000: # 345 days
|
||||||
return "%d months ago" % int(round(secDiff/2592000))
|
return QCoreApplication.translate(
|
||||||
|
"Common", "{0} months ago"
|
||||||
|
).format(int(round(secDiff/2592000)))
|
||||||
elif secDiff < 47336400: # 1.5 years
|
elif secDiff < 47336400: # 1.5 years
|
||||||
return "a year ago"
|
return QCoreApplication.translate(
|
||||||
|
"Common", "a year ago"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return "%d years ago" % int(round(secDiff/31557600))
|
return QCoreApplication.translate(
|
||||||
|
"Common", "{0} years ago"
|
||||||
|
).format(int(round(secDiff/31557600)))
|
||||||
|
|
||||||
def makeFileNameSafe(theText):
|
def makeFileNameSafe(theText):
|
||||||
"""Returns a filename safe version of the text.
|
"""Returns a filename safe version of the text.
|
||||||
@@ -311,3 +340,26 @@ def getGuiItem(theName):
|
|||||||
if qWidget.objectName() == theName:
|
if qWidget.objectName() == theName:
|
||||||
return qWidget
|
return qWidget
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def numberToRoman(numVal, isLower=False):
|
||||||
|
"""Convert an integer to a roman number.
|
||||||
|
"""
|
||||||
|
if not isinstance(numVal, int):
|
||||||
|
return "NAN"
|
||||||
|
if numVal < 1 or numVal > 4999:
|
||||||
|
return "OOR"
|
||||||
|
|
||||||
|
theValues = [
|
||||||
|
(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"),
|
||||||
|
(50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
|
||||||
|
]
|
||||||
|
|
||||||
|
romNum = ""
|
||||||
|
for theDiv, theSym in theValues:
|
||||||
|
n = numVal//theDiv
|
||||||
|
romNum += n*theSym
|
||||||
|
numVal -= n*theDiv
|
||||||
|
if numVal <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
return romNum.lower() if isLower else romNum
|
||||||
|
|||||||
+3
-5
@@ -76,7 +76,6 @@ class Config:
|
|||||||
self.themeRoot = None # The full path to the nw/assets/themes folder
|
self.themeRoot = None # The full path to the nw/assets/themes folder
|
||||||
self.dictPath = None # The full path to the nw/assets/dict folder
|
self.dictPath = None # The full path to the nw/assets/dict folder
|
||||||
self.iconPath = None # The full path to the nw/assets/icons folder
|
self.iconPath = None # The full path to the nw/assets/icons folder
|
||||||
self.langPath = None # The full path to the nw/assets/lang folder
|
|
||||||
self.helpPath = None # The full path to the novelwriter .qhc help file
|
self.helpPath = None # The full path to the novelwriter .qhc help file
|
||||||
|
|
||||||
# Runtime Settings and Variables
|
# Runtime Settings and Variables
|
||||||
@@ -298,7 +297,6 @@ class Config:
|
|||||||
self.themeRoot = os.path.join(self.assetPath, "themes")
|
self.themeRoot = os.path.join(self.assetPath, "themes")
|
||||||
self.dictPath = os.path.join(self.assetPath, "dict")
|
self.dictPath = os.path.join(self.assetPath, "dict")
|
||||||
self.iconPath = os.path.join(self.assetPath, "icons")
|
self.iconPath = os.path.join(self.assetPath, "icons")
|
||||||
self.langPath = os.path.join(self.assetPath, "lang")
|
|
||||||
self.appIcon = os.path.join(self.iconPath, "novelwriter.svg")
|
self.appIcon = os.path.join(self.iconPath, "novelwriter.svg")
|
||||||
|
|
||||||
# Internationalisation
|
# Internationalisation
|
||||||
@@ -384,7 +382,7 @@ class Config:
|
|||||||
for lngPath, lngBase in langList:
|
for lngPath, lngBase in langList:
|
||||||
for lngCode in self.qLocal.uiLanguages():
|
for lngCode in self.qLocal.uiLanguages():
|
||||||
qTrans = QTranslator()
|
qTrans = QTranslator()
|
||||||
lngFile = "%s_%s" % (lngBase, lngCode)
|
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
|
||||||
if lngFile not in self.qtTrans:
|
if lngFile not in self.qtTrans:
|
||||||
if qTrans.load(lngFile, lngPath):
|
if qTrans.load(lngFile, lngPath):
|
||||||
logger.debug("Loaded: %s" % qTrans.filePath())
|
logger.debug("Loaded: %s" % qTrans.filePath())
|
||||||
@@ -1079,7 +1077,7 @@ class Config:
|
|||||||
try:
|
try:
|
||||||
import enchant # noqa: F401
|
import enchant # noqa: F401
|
||||||
self.hasEnchant = True
|
self.hasEnchant = True
|
||||||
logger.debug("Checking package 'pyenchant': Ok")
|
logger.debug("Checking package 'pyenchant': OK")
|
||||||
except Exception:
|
except Exception:
|
||||||
self.hasEnchant = False
|
self.hasEnchant = False
|
||||||
logger.debug("Checking package 'pyenchant': Missing")
|
logger.debug("Checking package 'pyenchant': Missing")
|
||||||
@@ -1087,7 +1085,7 @@ class Config:
|
|||||||
assistPath = shutil.which("assistant")
|
assistPath = shutil.which("assistant")
|
||||||
self.hasAssistant = assistPath is not None
|
self.hasAssistant = assistPath is not None
|
||||||
if self.hasAssistant:
|
if self.hasAssistant:
|
||||||
logger.debug("Checking executable 'assistant': Ok")
|
logger.debug("Checking executable 'assistant': OK")
|
||||||
else:
|
else:
|
||||||
logger.debug("Checking executable 'assistant': Missing")
|
logger.debug("Checking executable 'assistant': Missing")
|
||||||
|
|
||||||
|
|||||||
@@ -240,8 +240,8 @@ class nwQuotes():
|
|||||||
|
|
||||||
"\u2039" : QT_TRANSLATE_NOOP("Constant", "Single left-pointing angle quotation mark"),
|
"\u2039" : QT_TRANSLATE_NOOP("Constant", "Single left-pointing angle quotation mark"),
|
||||||
"\u203a" : QT_TRANSLATE_NOOP("Constant", "Single right-pointing angle quotation mark"),
|
"\u203a" : QT_TRANSLATE_NOOP("Constant", "Single right-pointing angle quotation mark"),
|
||||||
"\u00ab" : QT_TRANSLATE_NOOP("Constant", "Left-pointing double angle quotation mark"),
|
"\u00ab" : QT_TRANSLATE_NOOP("Constant", "Double left-pointing angle quotation mark"),
|
||||||
"\u00bb" : QT_TRANSLATE_NOOP("Constant", "Right-pointing double angle quotation mark"),
|
"\u00bb" : QT_TRANSLATE_NOOP("Constant", "Double right-pointing angle quotation mark"),
|
||||||
|
|
||||||
"\u300c" : QT_TRANSLATE_NOOP("Constant", "Left corner bracket"),
|
"\u300c" : QT_TRANSLATE_NOOP("Constant", "Left corner bracket"),
|
||||||
"\u300d" : QT_TRANSLATE_NOOP("Constant", "Right corner bracket"),
|
"\u300d" : QT_TRANSLATE_NOOP("Constant", "Right corner bracket"),
|
||||||
|
|||||||
+1
-3
@@ -1,17 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.core.index import NWIndex
|
from nw.core.index import NWIndex, countWords
|
||||||
from nw.core.project import NWProject
|
from nw.core.project import NWProject
|
||||||
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
|
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
|
||||||
from nw.core.tohtml import ToHtml
|
from nw.core.tohtml import ToHtml
|
||||||
from nw.core.toodt import ToOdt
|
from nw.core.toodt import ToOdt
|
||||||
from nw.core.tomd import ToMarkdown
|
from nw.core.tomd import ToMarkdown
|
||||||
from nw.core.tools import countWords, numberToRoman
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"countWords",
|
"countWords",
|
||||||
"numberToRoman",
|
|
||||||
"NWDoc",
|
"NWDoc",
|
||||||
"NWIndex",
|
"NWIndex",
|
||||||
"NWProject",
|
"NWProject",
|
||||||
|
|||||||
+63
-3
@@ -5,7 +5,8 @@ novelWriter – Project Index
|
|||||||
Data class for the project index of tags, headers and references
|
Data class for the project index of tags, headers and references
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2019-05-27 [0.1.4]
|
Created: 2019-04-22 [0.0.1] countWords
|
||||||
|
Created: 2019-05-27 [0.1.4] NWIndex
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||||
@@ -32,10 +33,10 @@ import os
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwFiles, nwKeyWords, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
nwFiles, nwKeyWords, nwItemType, nwItemClass, nwItemLayout, nwAlert,
|
||||||
|
nwUnicode
|
||||||
)
|
)
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.core.tools import countWords
|
|
||||||
from nw.common import isHandle, isTitleTag, isItemClass, isItemLayout
|
from nw.common import isHandle, isTitleTag, isItemClass, isItemLayout
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -906,3 +907,62 @@ class NWIndex():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# END Class NWIndex
|
# END Class NWIndex
|
||||||
|
|
||||||
|
# =============================================================================================== #
|
||||||
|
# Simple Word Counter
|
||||||
|
# =============================================================================================== #
|
||||||
|
|
||||||
|
def countWords(theText):
|
||||||
|
"""Count words in a piece of text, skipping special syntax and
|
||||||
|
comments.
|
||||||
|
"""
|
||||||
|
charCount = 0
|
||||||
|
wordCount = 0
|
||||||
|
paraCount = 0
|
||||||
|
prevEmpty = True
|
||||||
|
|
||||||
|
# We need to treat dashes as word separators for counting words.
|
||||||
|
# The check+replace apprach is much faster that direct replace for
|
||||||
|
# large texts, and a bit slower for small texts, but in the latter
|
||||||
|
# case it doesn't matter.
|
||||||
|
if nwUnicode.U_ENDASH in theText:
|
||||||
|
theText = theText.replace(nwUnicode.U_ENDASH, " ")
|
||||||
|
if nwUnicode.U_EMDASH in theText:
|
||||||
|
theText = theText.replace(nwUnicode.U_EMDASH, " ")
|
||||||
|
|
||||||
|
for aLine in theText.splitlines():
|
||||||
|
|
||||||
|
countPara = True
|
||||||
|
theLen = len(aLine)
|
||||||
|
|
||||||
|
if theLen == 0:
|
||||||
|
prevEmpty = True
|
||||||
|
continue
|
||||||
|
if aLine[0] == "@" or aLine[0] == "%":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if aLine[0:5] == "#### ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 5
|
||||||
|
countPara = False
|
||||||
|
elif aLine[0:4] == "### ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 4
|
||||||
|
countPara = False
|
||||||
|
elif aLine[0:3] == "## ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 3
|
||||||
|
countPara = False
|
||||||
|
elif aLine[0:2] == "# ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 2
|
||||||
|
countPara = False
|
||||||
|
|
||||||
|
wordCount += len(aLine.split())
|
||||||
|
charCount += theLen
|
||||||
|
if countPara and prevEmpty:
|
||||||
|
paraCount += 1
|
||||||
|
|
||||||
|
prevEmpty = not countPara
|
||||||
|
|
||||||
|
return charCount, wordCount, paraCount
|
||||||
|
|||||||
+10
-45
@@ -203,7 +203,7 @@ class NWProject():
|
|||||||
self.autoReplace = {}
|
self.autoReplace = {}
|
||||||
self.titleFormat = {
|
self.titleFormat = {
|
||||||
"title" : r"%title%",
|
"title" : r"%title%",
|
||||||
"chapter" : self.tr(r"Chapter %ch%: %title%"),
|
"chapter" : self.tr("Chapter")+r" %ch%: %title%",
|
||||||
"unnumbered" : r"%title%",
|
"unnumbered" : r"%title%",
|
||||||
"scene" : r"* * *",
|
"scene" : r"* * *",
|
||||||
"section" : r"",
|
"section" : r"",
|
||||||
@@ -443,16 +443,10 @@ class NWProject():
|
|||||||
xRoot = nwXML.getroot()
|
xRoot = nwXML.getroot()
|
||||||
nwxRoot = xRoot.tag
|
nwxRoot = xRoot.tag
|
||||||
|
|
||||||
appVersion = xRoot.attrib.get("appVersion", self.tr("Unknown"))
|
appVersion = xRoot.attrib.get("appVersion", self.tr("Unknown"))
|
||||||
hexVersion = xRoot.attrib.get("hexVersion", "0x0")
|
hexVersion = xRoot.attrib.get("hexVersion", "0x0")
|
||||||
fileVersion = xRoot.attrib.get("fileVersion", self.tr("Unknown"))
|
fileVersion = xRoot.attrib.get("fileVersion", self.tr("Unknown"))
|
||||||
|
|
||||||
# The following are deprecated and will be removed
|
|
||||||
# The settings have been moved to the <project> tag
|
|
||||||
self.saveCount = checkInt(xRoot.attrib.get("saveCount", 0), 0, False)
|
|
||||||
self.autoCount = checkInt(xRoot.attrib.get("autoCount", 0), 0, False)
|
|
||||||
self.editTime = checkInt(xRoot.attrib.get("editTime", 0), 0, False)
|
|
||||||
|
|
||||||
logger.verbose("XML root is %s" % nwxRoot)
|
logger.verbose("XML root is %s" % nwxRoot)
|
||||||
logger.verbose("File version is %s" % fileVersion)
|
logger.verbose("File version is %s" % fileVersion)
|
||||||
|
|
||||||
@@ -479,27 +473,7 @@ class NWProject():
|
|||||||
# parser will lose the autoReplace settings if allowed to
|
# parser will lose the autoReplace settings if allowed to
|
||||||
# read the file. Introduced in version 0.10.
|
# read the file. Introduced in version 0.10.
|
||||||
|
|
||||||
if fileVersion == "1.0":
|
if fileVersion not in ("1.0", "1.1", "1.2"):
|
||||||
msgYes = self.theParent.askQuestion(
|
|
||||||
self.tr("Old Project Version"),
|
|
||||||
"%s<br><br>%s" % (
|
|
||||||
self.tr(
|
|
||||||
"The project file and data is created by a novelWriter version "
|
|
||||||
"lower than 0.7. Do you want to upgrade the project to the "
|
|
||||||
"most recent format?"
|
|
||||||
),
|
|
||||||
self.tr(
|
|
||||||
"Note that after the upgrade, you cannot open the project with "
|
|
||||||
"an older version of novelWriter any more, so make sure you "
|
|
||||||
"have a recent backup."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not msgYes:
|
|
||||||
self.clearProject()
|
|
||||||
return False
|
|
||||||
|
|
||||||
elif fileVersion != "1.1" and fileVersion != "1.2":
|
|
||||||
self.makeAlert((
|
self.makeAlert((
|
||||||
self.tr(
|
self.tr(
|
||||||
"Unknown or unsupported novelWriter project file format. "
|
"Unknown or unsupported novelWriter project file format. "
|
||||||
@@ -518,7 +492,7 @@ class NWProject():
|
|||||||
self.tr("Version Conflict"),
|
self.tr("Version Conflict"),
|
||||||
self.tr(
|
self.tr(
|
||||||
"This project was saved by a newer version of novelWriter, version "
|
"This project was saved by a newer version of novelWriter, version "
|
||||||
"{0}. This is version {1}. If you continue to open the project, "
|
"{0}. This is version {1}. If you continue to open the project, "
|
||||||
"some attributes and settings may not be preserved, but the "
|
"some attributes and settings may not be preserved, but the "
|
||||||
"overall project should be fine. Continue opening the project?"
|
"overall project should be fine. Continue opening the project?"
|
||||||
).format(appVersion, nw.__version__)
|
).format(appVersion, nw.__version__)
|
||||||
@@ -552,10 +526,6 @@ class NWProject():
|
|||||||
elif xItem.tag == "editTime":
|
elif xItem.tag == "editTime":
|
||||||
self.editTime = checkInt(xItem.text, 0)
|
self.editTime = checkInt(xItem.text, 0)
|
||||||
|
|
||||||
# The following is deprecated, and will be removed
|
|
||||||
elif xItem.tag == "backup":
|
|
||||||
self.doBackup = checkBool(xItem.text, False)
|
|
||||||
|
|
||||||
elif xChild.tag == "settings":
|
elif xChild.tag == "settings":
|
||||||
logger.debug("Found project settings")
|
logger.debug("Found project settings")
|
||||||
for xItem in xChild:
|
for xItem in xChild:
|
||||||
@@ -585,13 +555,8 @@ class NWProject():
|
|||||||
self.importItems.unpackXML(xItem)
|
self.importItems.unpackXML(xItem)
|
||||||
elif xItem.tag == "autoReplace":
|
elif xItem.tag == "autoReplace":
|
||||||
for xEntry in xItem:
|
for xEntry in xItem:
|
||||||
if xEntry.tag == "entry":
|
if xEntry.tag == "entry" and "key" in xEntry.attrib:
|
||||||
if "key" in xEntry.attrib:
|
self.autoReplace[xEntry.attrib["key"]] = checkString(
|
||||||
self.autoReplace[xEntry.attrib["key"]] = checkString(
|
|
||||||
xEntry.text, None, False
|
|
||||||
)
|
|
||||||
else: # Old format
|
|
||||||
self.autoReplace[xEntry.tag] = checkString(
|
|
||||||
xEntry.text, None, False
|
xEntry.text, None, False
|
||||||
)
|
)
|
||||||
elif xItem.tag == "titleFormat":
|
elif xItem.tag == "titleFormat":
|
||||||
@@ -1254,9 +1219,9 @@ class NWProject():
|
|||||||
theLang = "en"
|
theLang = "en"
|
||||||
|
|
||||||
lngShort = theLang.split("_")[0]
|
lngShort = theLang.split("_")[0]
|
||||||
loadFile = os.path.join(self.mainConf.langPath, "project_en.json")
|
loadFile = os.path.join(self.mainConf.nwLangPath, "project_en.json")
|
||||||
chkFile1 = os.path.join(self.mainConf.langPath, "project_%s.json" % theLang)
|
chkFile1 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % theLang)
|
||||||
chkFile2 = os.path.join(self.mainConf.langPath, "project_%s.json" % lngShort)
|
chkFile2 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % lngShort)
|
||||||
|
|
||||||
if os.path.isfile(chkFile1):
|
if os.path.isfile(chkFile1):
|
||||||
loadFile = chkFile1
|
loadFile = chkFile1
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from functools import partial
|
|||||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||||
|
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.core.tools import numberToRoman
|
from nw.common import numberToRoman
|
||||||
from nw.constants import nwConst, nwUnicode, nwItemLayout, nwItemType, nwRegEx
|
from nw.constants import nwConst, nwUnicode, nwItemLayout, nwItemType, nwRegEx
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
+1
-1
@@ -563,7 +563,7 @@ class ToOdt(Tokenizer):
|
|||||||
##
|
##
|
||||||
|
|
||||||
if len(theText) != len(theFmt):
|
if len(theText) != len(theFmt):
|
||||||
# Genrate dummy format if there isn't any
|
# Generate dummy format if there isn't any or it doesn't match
|
||||||
theFmt = " "*len(theText)
|
theFmt = " "*len(theText)
|
||||||
|
|
||||||
# XML functions
|
# XML functions
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
novelWriter – Various Tools
|
|
||||||
===========================
|
|
||||||
Various core tool functions
|
|
||||||
|
|
||||||
File History:
|
|
||||||
Created: 2019-04-22 [0.0.1] countWords
|
|
||||||
Created: 2020-07-05 [0.10.0] numberToRoman
|
|
||||||
|
|
||||||
This file is a part of novelWriter
|
|
||||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but
|
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from nw.constants import nwUnicode
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# =============================================================================================== #
|
|
||||||
# Simple Word Counter
|
|
||||||
# =============================================================================================== #
|
|
||||||
|
|
||||||
def countWords(theText):
|
|
||||||
"""Count words in a piece of text, skipping special syntax and
|
|
||||||
comments.
|
|
||||||
"""
|
|
||||||
charCount = 0
|
|
||||||
wordCount = 0
|
|
||||||
paraCount = 0
|
|
||||||
prevEmpty = True
|
|
||||||
|
|
||||||
# We need to treat dashes as word separators for counting words.
|
|
||||||
# The check+replace apprach is much faster that direct replace for
|
|
||||||
# large texts, and a bit slower for small texts, but in the latter
|
|
||||||
# case it doesn't matter.
|
|
||||||
if nwUnicode.U_ENDASH in theText:
|
|
||||||
theText = theText.replace(nwUnicode.U_ENDASH, " ")
|
|
||||||
if nwUnicode.U_EMDASH in theText:
|
|
||||||
theText = theText.replace(nwUnicode.U_EMDASH, " ")
|
|
||||||
|
|
||||||
for aLine in theText.splitlines():
|
|
||||||
|
|
||||||
countPara = True
|
|
||||||
theLen = len(aLine)
|
|
||||||
|
|
||||||
if theLen == 0:
|
|
||||||
prevEmpty = True
|
|
||||||
continue
|
|
||||||
if aLine[0] == "@" or aLine[0] == "%":
|
|
||||||
continue
|
|
||||||
|
|
||||||
if aLine[0:5] == "#### ":
|
|
||||||
wordCount -= 1
|
|
||||||
charCount -= 5
|
|
||||||
countPara = False
|
|
||||||
elif aLine[0:4] == "### ":
|
|
||||||
wordCount -= 1
|
|
||||||
charCount -= 4
|
|
||||||
countPara = False
|
|
||||||
elif aLine[0:3] == "## ":
|
|
||||||
wordCount -= 1
|
|
||||||
charCount -= 3
|
|
||||||
countPara = False
|
|
||||||
elif aLine[0:2] == "# ":
|
|
||||||
wordCount -= 1
|
|
||||||
charCount -= 2
|
|
||||||
countPara = False
|
|
||||||
|
|
||||||
wordCount += len(aLine.split())
|
|
||||||
charCount += theLen
|
|
||||||
if countPara and prevEmpty:
|
|
||||||
paraCount += 1
|
|
||||||
|
|
||||||
prevEmpty = not countPara
|
|
||||||
|
|
||||||
return charCount, wordCount, paraCount
|
|
||||||
|
|
||||||
# =============================================================================================== #
|
|
||||||
# Convert an Integer to a Roman Number
|
|
||||||
# =============================================================================================== #
|
|
||||||
|
|
||||||
def numberToRoman(numVal, isLower=False):
|
|
||||||
"""Convert an integer to a roman number.
|
|
||||||
"""
|
|
||||||
if not isinstance(numVal, int):
|
|
||||||
return "NAN"
|
|
||||||
if numVal < 1 or numVal > 4999:
|
|
||||||
return "OOR"
|
|
||||||
|
|
||||||
theValues = [
|
|
||||||
(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"),
|
|
||||||
(50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
|
|
||||||
]
|
|
||||||
|
|
||||||
romNum = ""
|
|
||||||
for theDiv, theSym in theValues:
|
|
||||||
n = numVal//theDiv
|
|
||||||
romNum += n*theSym
|
|
||||||
numVal -= n*theDiv
|
|
||||||
if numVal <= 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
return romNum.lower() if isLower else romNum
|
|
||||||
+1
-1
@@ -257,7 +257,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
# Styling Options
|
# Styling Options
|
||||||
# ===============
|
# ===============
|
||||||
|
|
||||||
self.styleGroup = QGroupBox("Styling Options", self)
|
self.styleGroup = QGroupBox(self.tr("Styling Options"), self)
|
||||||
self.styleForm = QGridLayout(self)
|
self.styleForm = QGridLayout(self)
|
||||||
self.styleGroup.setLayout(self.styleForm)
|
self.styleGroup.setLayout(self.styleForm)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QStylePainter, QStyleOptionTab, QListWidget, QListWidgetItem, QFrame
|
QStylePainter, QStyleOptionTab, QListWidget, QListWidgetItem, QFrame
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwUnicode, nwQuotes
|
from nw.constants import trConst, nwUnicode, nwQuotes
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ class QuotesDialog(QDialog):
|
|||||||
|
|
||||||
minSize = 100
|
minSize = 100
|
||||||
for sKey, sLabel in nwQuotes.SYMBOLS.items():
|
for sKey, sLabel in nwQuotes.SYMBOLS.items():
|
||||||
theText = "[ %s ] %s" % (sKey, sLabel)
|
theText = "[ %s ] %s" % (sKey, trConst(sLabel))
|
||||||
minSize = max(minSize, qMetrics.boundingRect(theText).width())
|
minSize = max(minSize, qMetrics.boundingRect(theText).width())
|
||||||
qtItem = QListWidgetItem(theText)
|
qtItem = QListWidgetItem(theText)
|
||||||
qtItem.setData(Qt.UserRole, sKey)
|
qtItem.setData(Qt.UserRole, sKey)
|
||||||
@@ -500,7 +500,7 @@ class QuotesDialog(QDialog):
|
|||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
||||||
self.buttonBox.accepted.connect(self._doAccept)
|
self.buttonBox.accepted.connect(self._doAccept)
|
||||||
self.buttonBox.rejected.connect(self._doReject)
|
self.buttonBox.rejected.connect(self._doReject)
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ class GuiDocMerge(QDialog):
|
|||||||
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
|
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
|
||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
||||||
self.buttonBox.accepted.connect(self._doMerge)
|
self.buttonBox.accepted.connect(self._doMerge)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@ class GuiDocSplit(QDialog):
|
|||||||
self.splitLevel.currentIndexChanged.connect(self._populateList)
|
self.splitLevel.currentIndexChanged.connect(self._populateList)
|
||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
||||||
self.buttonBox.accepted.connect(self._doSplit)
|
self.buttonBox.accepted.connect(self._doSplit)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class GuiItemEditor(QDialog):
|
|||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
||||||
self.buttonBox.accepted.connect(self._doSave)
|
self.buttonBox.accepted.connect(self._doSave)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class GuiPreferences(PagedDialog):
|
|||||||
self.addTab(self.tabAuto, self.tr("Automation"))
|
self.addTab(self.tabAuto, self.tr("Automation"))
|
||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
||||||
self.buttonBox.accepted.connect(self._doSave)
|
self.buttonBox.accepted.connect(self._doSave)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
@@ -828,8 +828,7 @@ class GuiPreferencesEditor(QWidget):
|
|||||||
for spTag, spProv in theDict.listDictionaries():
|
for spTag, spProv in theDict.listDictionaries():
|
||||||
qLocal = QLocale(spTag)
|
qLocal = QLocale(spTag)
|
||||||
spLang = qLocal.nativeLanguageName().title()
|
spLang = qLocal.nativeLanguageName().title()
|
||||||
spName = qLocal.bcp47Name()
|
self.spellLangList.addItem("%s [%s]" % (spLang, spProv), spTag)
|
||||||
self.spellLangList.addItem("%s (%s) [%s]" % (spLang, spName, spProv), spTag)
|
|
||||||
|
|
||||||
spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
|
spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
|
||||||
if spellIdx != -1:
|
if spellIdx != -1:
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
|
|||||||
|
|
||||||
from nw.gui.custom import PagedDialog, QSwitch
|
from nw.gui.custom import PagedDialog, QSwitch
|
||||||
from nw.constants import nwUnicode
|
from nw.constants import nwUnicode
|
||||||
from nw.core import numberToRoman
|
from nw.common import numberToRoman
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
import nw
|
import nw
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt, QLocale
|
||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
|
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QHBoxLayout, QVBoxLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget,
|
QHBoxLayout, QVBoxLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget,
|
||||||
@@ -77,7 +77,7 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
self.addTab(self.tabReplace, self.tr("Auto-Replace"))
|
self.addTab(self.tabReplace, self.tr("Auto-Replace"))
|
||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
|
self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
|
||||||
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
|
||||||
self.buttonBox.accepted.connect(self._doSave)
|
self.buttonBox.accepted.connect(self._doSave)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
@@ -207,11 +207,14 @@ class GuiProjectEditMain(QWidget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.spellLang = QComboBox(self)
|
self.spellLang = QComboBox(self)
|
||||||
|
self.spellLang.setFixedWidth(xW)
|
||||||
theDict = self.theParent.docEditor.theDict
|
theDict = self.theParent.docEditor.theDict
|
||||||
self.spellLang.addItem(self.tr("Default"), "None")
|
self.spellLang.addItem(self.tr("Default"), "None")
|
||||||
if theDict is not None:
|
if theDict is not None:
|
||||||
for spTag, spName in theDict.listDictionaries():
|
for spTag, spProv in theDict.listDictionaries():
|
||||||
self.spellLang.addItem(spName, spTag)
|
qLocal = QLocale(spTag)
|
||||||
|
spLang = qLocal.nativeLanguageName().title()
|
||||||
|
self.spellLang.addItem("%s [%s]" % (spLang, spProv), spTag)
|
||||||
|
|
||||||
self.mainForm.addRow(
|
self.mainForm.addRow(
|
||||||
self.tr("Spell check language"),
|
self.tr("Spell check language"),
|
||||||
|
|||||||
+2
-4
@@ -249,9 +249,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
if self.theProject.projTree.isTrashRoot(pHandle):
|
if self.theProject.projTree.isTrashRoot(pHandle):
|
||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
self.tr("Cannot add new files or folders to the {0} folder.").format(
|
self.tr("Cannot add new files or folders to the Trash folder."), nwAlert.ERROR
|
||||||
trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
|
|
||||||
), nwAlert.ERROR
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -974,7 +972,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
else:
|
else:
|
||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
self.tr(
|
self.tr(
|
||||||
"There is nowhere to add item with name '{0}'"
|
"There is nowhere to add item with name '{0}'."
|
||||||
).format(nwItem.itemName), nwAlert.ERROR
|
).format(nwItem.itemName), nwAlert.ERROR
|
||||||
)
|
)
|
||||||
del self._treeMap[tHandle]
|
del self._treeMap[tHandle]
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ class GuiWritingStats(QDialog):
|
|||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt = QFileDialog.Options()
|
||||||
dlgOpt |= QFileDialog.DontUseNativeDialog
|
dlgOpt |= QFileDialog.DontUseNativeDialog
|
||||||
savePath, _ = QFileDialog.getSaveFileName(
|
savePath, _ = QFileDialog.getSaveFileName(
|
||||||
self, self.tr("Save Document As"), savePath, options=dlgOpt
|
self, self.tr("Save Data As"), savePath, options=dlgOpt
|
||||||
)
|
)
|
||||||
if not savePath:
|
if not savePath:
|
||||||
return False
|
return False
|
||||||
|
|||||||
+1
-1
@@ -290,7 +290,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.openProject(self.mainConf.cmdOpen)
|
self.openProject(self.mainConf.cmdOpen)
|
||||||
|
|
||||||
logger.debug("novelWriter is ready ...")
|
logger.debug("novelWriter is ready ...")
|
||||||
self.setStatus("novelWriter is ready ...")
|
self.setStatus(self.tr("novelWriter is ready ..."))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from nw.common import (
|
|||||||
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
|
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
|
||||||
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
|
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
|
||||||
makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
|
makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
|
||||||
isItemLayout
|
isItemLayout, numberToRoman
|
||||||
)
|
)
|
||||||
from tools import cmpList
|
from tools import cmpList
|
||||||
|
|
||||||
@@ -325,3 +325,30 @@ def testBaseCommon_MakeFileNameSafe():
|
|||||||
assert makeFileNameSafe("aaaa bbbb") == "aaaa bbbb"
|
assert makeFileNameSafe("aaaa bbbb") == "aaaa bbbb"
|
||||||
|
|
||||||
# END Test testBaseCommon_MakeFileNameSafe
|
# END Test testBaseCommon_MakeFileNameSafe
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testBaseCommon_RomanNumbers():
|
||||||
|
"""Test conversion of integers to Roman numbers.
|
||||||
|
"""
|
||||||
|
assert numberToRoman(None, False) == "NAN"
|
||||||
|
assert numberToRoman(0, False) == "OOR"
|
||||||
|
assert numberToRoman(1, False) == "I"
|
||||||
|
assert numberToRoman(2, False) == "II"
|
||||||
|
assert numberToRoman(3, False) == "III"
|
||||||
|
assert numberToRoman(4, False) == "IV"
|
||||||
|
assert numberToRoman(5, False) == "V"
|
||||||
|
assert numberToRoman(6, False) == "VI"
|
||||||
|
assert numberToRoman(7, False) == "VII"
|
||||||
|
assert numberToRoman(8, False) == "VIII"
|
||||||
|
assert numberToRoman(9, False) == "IX"
|
||||||
|
assert numberToRoman(10, False) == "X"
|
||||||
|
assert numberToRoman(14, False) == "XIV"
|
||||||
|
assert numberToRoman(42, False) == "XLII"
|
||||||
|
assert numberToRoman(99, False) == "XCIX"
|
||||||
|
assert numberToRoman(142, False) == "CXLII"
|
||||||
|
assert numberToRoman(542, False) == "DXLII"
|
||||||
|
assert numberToRoman(999, False) == "CMXCIX"
|
||||||
|
assert numberToRoman(2010, False) == "MMX"
|
||||||
|
assert numberToRoman(999, True) == "cmxcix"
|
||||||
|
|
||||||
|
# END Test testBaseCommon_RomanNumbers
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from shutil import copyfile
|
|||||||
from tools import cmpFiles
|
from tools import cmpFiles
|
||||||
|
|
||||||
from nw.core.project import NWProject
|
from nw.core.project import NWProject
|
||||||
from nw.core.index import NWIndex
|
from nw.core.index import NWIndex, countWords
|
||||||
from nw.constants import nwItemClass, nwItemLayout
|
from nw.constants import nwItemClass, nwItemLayout
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
@@ -1161,3 +1161,34 @@ def testCoreIndex_CheckTextCounts(dummyGUI):
|
|||||||
theIndex._checkTextCounts()
|
theIndex._checkTextCounts()
|
||||||
|
|
||||||
# END Test testCoreIndex_CheckTextCounts
|
# END Test testCoreIndex_CheckTextCounts
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreIndex_CountWords():
|
||||||
|
"""Test the word counter and the exclusion filers.
|
||||||
|
"""
|
||||||
|
testText = (
|
||||||
|
"# Heading One\n"
|
||||||
|
"## Heading Two\n"
|
||||||
|
"### Heading Three\n"
|
||||||
|
"#### Heading Four\n"
|
||||||
|
"\n"
|
||||||
|
"@tag: value\n"
|
||||||
|
"\n"
|
||||||
|
"% A comment that should n ot be counted.\n"
|
||||||
|
"\n"
|
||||||
|
"The first paragraph.\n"
|
||||||
|
"\n"
|
||||||
|
"The second paragraph.\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"The third paragraph.\n"
|
||||||
|
"\n"
|
||||||
|
"Dashes\u2013and even longer\u2014dashes."
|
||||||
|
)
|
||||||
|
cC, wC, pC = countWords(testText)
|
||||||
|
|
||||||
|
assert cC == 138
|
||||||
|
assert wC == 22
|
||||||
|
assert pC == 4
|
||||||
|
|
||||||
|
# END Test testCoreIndex_CountWords
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
|
|||||||
|
|
||||||
# Wrong file version
|
# Wrong file version
|
||||||
writeFile(rName, (
|
writeFile(rName, (
|
||||||
"<?xml version='1.0' encoding='utf-8'?>\n"
|
"<?xml version='0.0' encoding='utf-8'?>\n"
|
||||||
"<novelWriterXML "
|
"<novelWriterXML "
|
||||||
"appVersion=\"1.0\" "
|
"appVersion=\"1.0\" "
|
||||||
"hexVersion=\"0x01000000\" "
|
"hexVersion=\"0x01000000\" "
|
||||||
@@ -421,25 +421,6 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
|
|||||||
assert theProject.openProject(nwMinimal) is True
|
assert theProject.openProject(nwMinimal) is True
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|
||||||
# Test deprecated XML entries
|
|
||||||
writeFile(rName, (
|
|
||||||
"<?xml version='1.0' encoding='utf-8'?>\n"
|
|
||||||
"<novelWriterXML "
|
|
||||||
"appVersion=\"1.0\" "
|
|
||||||
"hexVersion=\"0x01000000\" "
|
|
||||||
"fileVersion=\"1.2\" "
|
|
||||||
"timeStamp=\"2020-01-01 00:00:00\">\n"
|
|
||||||
"<settings>\n"
|
|
||||||
"<autoReplace>\n"
|
|
||||||
"<A>B</A>\n"
|
|
||||||
"</autoReplace>\n"
|
|
||||||
"</settings>\n"
|
|
||||||
"</novelWriterXML>\n"
|
|
||||||
))
|
|
||||||
assert theProject.openProject(nwMinimal) is True
|
|
||||||
assert theProject.autoReplace == {"A": "B"}
|
|
||||||
assert theProject.closeProject()
|
|
||||||
|
|
||||||
# Clean up XML files
|
# Clean up XML files
|
||||||
os.unlink(rName)
|
os.unlink(rName)
|
||||||
os.unlink(bName)
|
os.unlink(bName)
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
novelWriter – Core Tools Tester
|
|
||||||
===============================
|
|
||||||
|
|
||||||
This file is a part of novelWriter
|
|
||||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but
|
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from nw.core.tools import countWords, numberToRoman
|
|
||||||
|
|
||||||
@pytest.mark.core
|
|
||||||
def testCoreTools_CountWords():
|
|
||||||
"""Test the word counter and the exclusion filers.
|
|
||||||
"""
|
|
||||||
testText = (
|
|
||||||
"# Heading One\n"
|
|
||||||
"## Heading Two\n"
|
|
||||||
"### Heading Three\n"
|
|
||||||
"#### Heading Four\n"
|
|
||||||
"\n"
|
|
||||||
"@tag: value\n"
|
|
||||||
"\n"
|
|
||||||
"% A comment that should n ot be counted.\n"
|
|
||||||
"\n"
|
|
||||||
"The first paragraph.\n"
|
|
||||||
"\n"
|
|
||||||
"The second paragraph.\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"The third paragraph.\n"
|
|
||||||
"\n"
|
|
||||||
"Dashes\u2013and even longer\u2014dashes."
|
|
||||||
)
|
|
||||||
cC, wC, pC = countWords(testText)
|
|
||||||
|
|
||||||
assert cC == 138
|
|
||||||
assert wC == 22
|
|
||||||
assert pC == 4
|
|
||||||
|
|
||||||
# END Test testCoreTools_CountWords
|
|
||||||
|
|
||||||
@pytest.mark.core
|
|
||||||
def testCoreTools_RomanNumbers():
|
|
||||||
"""Test conversion of integers to Roman numbers.
|
|
||||||
"""
|
|
||||||
assert numberToRoman(None, False) == "NAN"
|
|
||||||
assert numberToRoman(0, False) == "OOR"
|
|
||||||
assert numberToRoman(1, False) == "I"
|
|
||||||
assert numberToRoman(2, False) == "II"
|
|
||||||
assert numberToRoman(3, False) == "III"
|
|
||||||
assert numberToRoman(4, False) == "IV"
|
|
||||||
assert numberToRoman(5, False) == "V"
|
|
||||||
assert numberToRoman(6, False) == "VI"
|
|
||||||
assert numberToRoman(7, False) == "VII"
|
|
||||||
assert numberToRoman(8, False) == "VIII"
|
|
||||||
assert numberToRoman(9, False) == "IX"
|
|
||||||
assert numberToRoman(10, False) == "X"
|
|
||||||
assert numberToRoman(14, False) == "XIV"
|
|
||||||
assert numberToRoman(42, False) == "XLII"
|
|
||||||
assert numberToRoman(99, False) == "XCIX"
|
|
||||||
assert numberToRoman(142, False) == "CXLII"
|
|
||||||
assert numberToRoman(542, False) == "DXLII"
|
|
||||||
assert numberToRoman(999, False) == "CMXCIX"
|
|
||||||
assert numberToRoman(2010, False) == "MMX"
|
|
||||||
assert numberToRoman(999, True) == "cmxcix"
|
|
||||||
|
|
||||||
# END Test testCoreTools_RomanNumbers
|
|
||||||
Reference in New Issue
Block a user