Merge pull request #679 from vkbo/norwegian_locale

Norwegian Localisation
This commit is contained in:
Veronica Berglyd Olsen
2021-02-17 23:25:19 +01:00
committed by GitHub
32 changed files with 5101 additions and 358 deletions
+52
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+47 -36
View File
@@ -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>
+5
View File
@@ -0,0 +1,5 @@
{
"Synopsis": "Sinopse",
"Comment": "Comentários",
"Notes": "Notas"
}
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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 20182021, Veronica Berglyd Olsen Copyright 20182021, 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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
-118
View File
@@ -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 20182021, 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -3
View File
@@ -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:
+1 -1
View File
@@ -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__)
+7 -4
View File
@@ -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
View File
@@ -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]
+1 -1
View File
@@ -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
View File
@@ -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
+28 -1
View File
@@ -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
+32 -1
View File
@@ -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
+1 -20
View File
@@ -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)
-83
View File
@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""
novelWriter Core Tools Tester
===============================
This file is a part of novelWriter
Copyright 20182021, 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