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"?>
|
||||
<!DOCTYPE TS><TS version="2.0" language="pt" sourcelanguage="en">
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="pt" sourcelanguage="en">
|
||||
<context>
|
||||
<name>Constant</name>
|
||||
<message>
|
||||
@@ -678,6 +679,11 @@
|
||||
<source>JSON + novelWriter Markdown</source>
|
||||
<translation>JSON + Markdown do novelWriter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/build.py" line="260"/>
|
||||
<source>Styling Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GuiBuildNovelDocView</name>
|
||||
@@ -966,16 +972,16 @@
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/docmerge.py" line="69"/>
|
||||
<source>Ok</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/docmerge.py" line="70"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/docmerge.py" line="69"/>
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<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>
|
||||
<translation>O documento será dividio em {0} arquivo(s) em um novo diretório. O documento original será mantido intacto.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/docsplit.py" line="85"/>
|
||||
<source>Ok</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/docsplit.py" line="86"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/docsplit.py" line="85"/>
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GuiDocViewFooter</name>
|
||||
@@ -1232,16 +1238,16 @@
|
||||
<source>Layout</source>
|
||||
<translation>Leiaute</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/itemeditor.py" line="120"/>
|
||||
<source>Ok</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/itemeditor.py" line="121"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/itemeditor.py" line="120"/>
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GuiMain</name>
|
||||
@@ -1450,6 +1456,11 @@
|
||||
<source>All files ({0})</source>
|
||||
<translation>Todos os arquivos ({0})</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/guimain.py" line="293"/>
|
||||
<source>novelWriter is ready ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GuiMainMenu</name>
|
||||
@@ -2733,16 +2744,16 @@
|
||||
<source>Automation</source>
|
||||
<translation>Automação</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/preferences.py" line="73"/>
|
||||
<source>Ok</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/preferences.py" line="74"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/preferences.py" line="73"/>
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GuiPreferencesAutomation</name>
|
||||
@@ -3740,16 +3751,16 @@
|
||||
<source>Auto-Replace</source>
|
||||
<translation>Substituição Automática</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/projsettings.py" line="80"/>
|
||||
<source>Ok</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/projsettings.py" line="81"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/projsettings.py" line="80"/>
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>GuiProjectTree</name>
|
||||
@@ -4184,11 +4195,6 @@
|
||||
<source>Trash</source>
|
||||
<translation>Lixeira</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/core/project.py" line="206"/>
|
||||
<source>Chapter %ch%: %title%</source>
|
||||
<translation>Capítulo %ch%: %title%</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/core/project.py" line="219"/>
|
||||
<source>New</source>
|
||||
@@ -4489,6 +4495,11 @@
|
||||
<source>and</source>
|
||||
<translation>e</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/core/project.py" line="206"/>
|
||||
<source>Chapter</source>
|
||||
<translation>Capítulo</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ProjWizardCustomPage</name>
|
||||
@@ -4662,16 +4673,16 @@
|
||||
</context>
|
||||
<context>
|
||||
<name>QuotesDialog</name>
|
||||
<message>
|
||||
<location filename="../nw/gui/custom.py" line="503"/>
|
||||
<source>Ok</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/custom.py" line="504"/>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../nw/gui/custom.py" line="503"/>
|
||||
<source>OK</source>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Tokenizer</name>
|
||||
|
||||
@@ -102,4 +102,4 @@
|
||||
"97": "Ninety-Seven",
|
||||
"98": "Ninety-Eight",
|
||||
"99": "Ninety-Nine"
|
||||
}
|
||||
}
|
||||
@@ -102,4 +102,4 @@
|
||||
"97": "nittisju",
|
||||
"98": "nittiåtte",
|
||||
"99": "nittini"
|
||||
}
|
||||
}
|
||||
@@ -102,4 +102,4 @@
|
||||
"97": "nittisju",
|
||||
"98": "nittiåtte",
|
||||
"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/wordlist.py \
|
||||
nw/gui/writingstats.py \
|
||||
nw/common.py \
|
||||
nw/error.py \
|
||||
nw/guimain.py
|
||||
|
||||
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 PyQt5.QtWidgets import qApp
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from nw.constants import (
|
||||
nwConst, nwUnicode, nwItemClass, nwItemType, nwItemLayout
|
||||
@@ -267,33 +268,61 @@ def fuzzyTime(secDiff):
|
||||
"""Converts a time difference in seconds into a fuzzy time string.
|
||||
"""
|
||||
if secDiff < 0:
|
||||
return "in the future"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "in the future"
|
||||
)
|
||||
elif secDiff < 30:
|
||||
return "just now"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "just now"
|
||||
)
|
||||
elif secDiff < 90:
|
||||
return "a minute ago"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "a minute ago"
|
||||
)
|
||||
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
|
||||
return "an hour ago"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "an hour ago"
|
||||
)
|
||||
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
|
||||
return "a day ago"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "a day ago"
|
||||
)
|
||||
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
|
||||
return "a week ago"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "a week ago"
|
||||
)
|
||||
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
|
||||
return "a month ago"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "a month ago"
|
||||
)
|
||||
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
|
||||
return "a year ago"
|
||||
return QCoreApplication.translate(
|
||||
"Common", "a year ago"
|
||||
)
|
||||
else:
|
||||
return "%d years ago" % int(round(secDiff/31557600))
|
||||
return QCoreApplication.translate(
|
||||
"Common", "{0} years ago"
|
||||
).format(int(round(secDiff/31557600)))
|
||||
|
||||
def makeFileNameSafe(theText):
|
||||
"""Returns a filename safe version of the text.
|
||||
@@ -311,3 +340,26 @@ def getGuiItem(theName):
|
||||
if qWidget.objectName() == theName:
|
||||
return qWidget
|
||||
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.dictPath = None # The full path to the nw/assets/dict 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
|
||||
|
||||
# Runtime Settings and Variables
|
||||
@@ -298,7 +297,6 @@ class Config:
|
||||
self.themeRoot = os.path.join(self.assetPath, "themes")
|
||||
self.dictPath = os.path.join(self.assetPath, "dict")
|
||||
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")
|
||||
|
||||
# Internationalisation
|
||||
@@ -384,7 +382,7 @@ class Config:
|
||||
for lngPath, lngBase in langList:
|
||||
for lngCode in self.qLocal.uiLanguages():
|
||||
qTrans = QTranslator()
|
||||
lngFile = "%s_%s" % (lngBase, lngCode)
|
||||
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
|
||||
if lngFile not in self.qtTrans:
|
||||
if qTrans.load(lngFile, lngPath):
|
||||
logger.debug("Loaded: %s" % qTrans.filePath())
|
||||
@@ -1079,7 +1077,7 @@ class Config:
|
||||
try:
|
||||
import enchant # noqa: F401
|
||||
self.hasEnchant = True
|
||||
logger.debug("Checking package 'pyenchant': Ok")
|
||||
logger.debug("Checking package 'pyenchant': OK")
|
||||
except Exception:
|
||||
self.hasEnchant = False
|
||||
logger.debug("Checking package 'pyenchant': Missing")
|
||||
@@ -1087,7 +1085,7 @@ class Config:
|
||||
assistPath = shutil.which("assistant")
|
||||
self.hasAssistant = assistPath is not None
|
||||
if self.hasAssistant:
|
||||
logger.debug("Checking executable 'assistant': Ok")
|
||||
logger.debug("Checking executable 'assistant': OK")
|
||||
else:
|
||||
logger.debug("Checking executable 'assistant': Missing")
|
||||
|
||||
|
||||
@@ -240,8 +240,8 @@ class nwQuotes():
|
||||
|
||||
"\u2039" : QT_TRANSLATE_NOOP("Constant", "Single left-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"),
|
||||
"\u00bb" : QT_TRANSLATE_NOOP("Constant", "Right-pointing double angle quotation mark"),
|
||||
"\u00ab" : QT_TRANSLATE_NOOP("Constant", "Double left-pointing angle quotation mark"),
|
||||
"\u00bb" : QT_TRANSLATE_NOOP("Constant", "Double right-pointing angle quotation mark"),
|
||||
|
||||
"\u300c" : QT_TRANSLATE_NOOP("Constant", "Left corner bracket"),
|
||||
"\u300d" : QT_TRANSLATE_NOOP("Constant", "Right corner bracket"),
|
||||
|
||||
+1
-3
@@ -1,17 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
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.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
|
||||
from nw.core.tohtml import ToHtml
|
||||
from nw.core.toodt import ToOdt
|
||||
from nw.core.tomd import ToMarkdown
|
||||
from nw.core.tools import countWords, numberToRoman
|
||||
|
||||
__all__ = [
|
||||
"countWords",
|
||||
"numberToRoman",
|
||||
"NWDoc",
|
||||
"NWIndex",
|
||||
"NWProject",
|
||||
|
||||
+63
-3
@@ -5,7 +5,8 @@ novelWriter – Project Index
|
||||
Data class for the project index of tags, headers and references
|
||||
|
||||
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
|
||||
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||
@@ -32,10 +33,10 @@ import os
|
||||
from time import time
|
||||
|
||||
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.tools import countWords
|
||||
from nw.common import isHandle, isTitleTag, isItemClass, isItemLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -906,3 +907,62 @@ class NWIndex():
|
||||
return
|
||||
|
||||
# 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.titleFormat = {
|
||||
"title" : r"%title%",
|
||||
"chapter" : self.tr(r"Chapter %ch%: %title%"),
|
||||
"chapter" : self.tr("Chapter")+r" %ch%: %title%",
|
||||
"unnumbered" : r"%title%",
|
||||
"scene" : r"* * *",
|
||||
"section" : r"",
|
||||
@@ -443,16 +443,10 @@ class NWProject():
|
||||
xRoot = nwXML.getroot()
|
||||
nwxRoot = xRoot.tag
|
||||
|
||||
appVersion = xRoot.attrib.get("appVersion", self.tr("Unknown"))
|
||||
hexVersion = xRoot.attrib.get("hexVersion", "0x0")
|
||||
appVersion = xRoot.attrib.get("appVersion", self.tr("Unknown"))
|
||||
hexVersion = xRoot.attrib.get("hexVersion", "0x0")
|
||||
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("File version is %s" % fileVersion)
|
||||
|
||||
@@ -479,27 +473,7 @@ class NWProject():
|
||||
# parser will lose the autoReplace settings if allowed to
|
||||
# read the file. Introduced in version 0.10.
|
||||
|
||||
if fileVersion == "1.0":
|
||||
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":
|
||||
if fileVersion not in ("1.0", "1.1", "1.2"):
|
||||
self.makeAlert((
|
||||
self.tr(
|
||||
"Unknown or unsupported novelWriter project file format. "
|
||||
@@ -518,7 +492,7 @@ class NWProject():
|
||||
self.tr("Version Conflict"),
|
||||
self.tr(
|
||||
"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 "
|
||||
"overall project should be fine. Continue opening the project?"
|
||||
).format(appVersion, nw.__version__)
|
||||
@@ -552,10 +526,6 @@ class NWProject():
|
||||
elif xItem.tag == "editTime":
|
||||
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":
|
||||
logger.debug("Found project settings")
|
||||
for xItem in xChild:
|
||||
@@ -585,13 +555,8 @@ class NWProject():
|
||||
self.importItems.unpackXML(xItem)
|
||||
elif xItem.tag == "autoReplace":
|
||||
for xEntry in xItem:
|
||||
if xEntry.tag == "entry":
|
||||
if "key" in xEntry.attrib:
|
||||
self.autoReplace[xEntry.attrib["key"]] = checkString(
|
||||
xEntry.text, None, False
|
||||
)
|
||||
else: # Old format
|
||||
self.autoReplace[xEntry.tag] = checkString(
|
||||
if xEntry.tag == "entry" and "key" in xEntry.attrib:
|
||||
self.autoReplace[xEntry.attrib["key"]] = checkString(
|
||||
xEntry.text, None, False
|
||||
)
|
||||
elif xItem.tag == "titleFormat":
|
||||
@@ -1254,9 +1219,9 @@ class NWProject():
|
||||
theLang = "en"
|
||||
|
||||
lngShort = theLang.split("_")[0]
|
||||
loadFile = os.path.join(self.mainConf.langPath, "project_en.json")
|
||||
chkFile1 = os.path.join(self.mainConf.langPath, "project_%s.json" % theLang)
|
||||
chkFile2 = os.path.join(self.mainConf.langPath, "project_%s.json" % lngShort)
|
||||
loadFile = os.path.join(self.mainConf.nwLangPath, "project_en.json")
|
||||
chkFile1 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % theLang)
|
||||
chkFile2 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % lngShort)
|
||||
|
||||
if os.path.isfile(chkFile1):
|
||||
loadFile = chkFile1
|
||||
|
||||
@@ -33,7 +33,7 @@ from functools import partial
|
||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
+1
-1
@@ -563,7 +563,7 @@ class ToOdt(Tokenizer):
|
||||
##
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
# ===============
|
||||
|
||||
self.styleGroup = QGroupBox("Styling Options", self)
|
||||
self.styleGroup = QGroupBox(self.tr("Styling Options"), self)
|
||||
self.styleForm = QGridLayout(self)
|
||||
self.styleGroup.setLayout(self.styleForm)
|
||||
|
||||
|
||||
+3
-3
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
|
||||
QStylePainter, QStyleOptionTab, QListWidget, QListWidgetItem, QFrame
|
||||
)
|
||||
|
||||
from nw.constants import nwUnicode, nwQuotes
|
||||
from nw.constants import trConst, nwUnicode, nwQuotes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -487,7 +487,7 @@ class QuotesDialog(QDialog):
|
||||
|
||||
minSize = 100
|
||||
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())
|
||||
qtItem = QListWidgetItem(theText)
|
||||
qtItem.setData(Qt.UserRole, sKey)
|
||||
@@ -500,7 +500,7 @@ class QuotesDialog(QDialog):
|
||||
|
||||
# Buttons
|
||||
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.accepted.connect(self._doAccept)
|
||||
self.buttonBox.rejected.connect(self._doReject)
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ class GuiDocMerge(QDialog):
|
||||
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
|
||||
|
||||
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.accepted.connect(self._doMerge)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ class GuiDocSplit(QDialog):
|
||||
self.splitLevel.currentIndexChanged.connect(self._populateList)
|
||||
|
||||
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.accepted.connect(self._doSplit)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
|
||||
@@ -117,7 +117,7 @@ class GuiItemEditor(QDialog):
|
||||
|
||||
# Buttons
|
||||
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.accepted.connect(self._doSave)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
|
||||
@@ -70,7 +70,7 @@ class GuiPreferences(PagedDialog):
|
||||
self.addTab(self.tabAuto, self.tr("Automation"))
|
||||
|
||||
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.accepted.connect(self._doSave)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
@@ -828,8 +828,7 @@ class GuiPreferencesEditor(QWidget):
|
||||
for spTag, spProv in theDict.listDictionaries():
|
||||
qLocal = QLocale(spTag)
|
||||
spLang = qLocal.nativeLanguageName().title()
|
||||
spName = qLocal.bcp47Name()
|
||||
self.spellLangList.addItem("%s (%s) [%s]" % (spLang, spName, spProv), spTag)
|
||||
self.spellLangList.addItem("%s [%s]" % (spLang, spProv), spTag)
|
||||
|
||||
spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
|
||||
if spellIdx != -1:
|
||||
|
||||
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from nw.gui.custom import PagedDialog, QSwitch
|
||||
from nw.constants import nwUnicode
|
||||
from nw.core import numberToRoman
|
||||
from nw.common import numberToRoman
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import nw
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtCore import Qt, QLocale
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
|
||||
from PyQt5.QtWidgets import (
|
||||
QHBoxLayout, QVBoxLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget,
|
||||
@@ -77,7 +77,7 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.addTab(self.tabReplace, self.tr("Auto-Replace"))
|
||||
|
||||
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.accepted.connect(self._doSave)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
@@ -207,11 +207,14 @@ class GuiProjectEditMain(QWidget):
|
||||
)
|
||||
|
||||
self.spellLang = QComboBox(self)
|
||||
self.spellLang.setFixedWidth(xW)
|
||||
theDict = self.theParent.docEditor.theDict
|
||||
self.spellLang.addItem(self.tr("Default"), "None")
|
||||
if theDict is not None:
|
||||
for spTag, spName in theDict.listDictionaries():
|
||||
self.spellLang.addItem(spName, spTag)
|
||||
for spTag, spProv in theDict.listDictionaries():
|
||||
qLocal = QLocale(spTag)
|
||||
spLang = qLocal.nativeLanguageName().title()
|
||||
self.spellLang.addItem("%s [%s]" % (spLang, spProv), spTag)
|
||||
|
||||
self.mainForm.addRow(
|
||||
self.tr("Spell check language"),
|
||||
|
||||
+2
-4
@@ -249,9 +249,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
if self.theProject.projTree.isTrashRoot(pHandle):
|
||||
self.makeAlert(
|
||||
self.tr("Cannot add new files or folders to the {0} folder.").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
|
||||
), nwAlert.ERROR
|
||||
self.tr("Cannot add new files or folders to the Trash folder."), nwAlert.ERROR
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -974,7 +972,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
else:
|
||||
self.makeAlert(
|
||||
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
|
||||
)
|
||||
del self._treeMap[tHandle]
|
||||
|
||||
@@ -372,7 +372,7 @@ class GuiWritingStats(QDialog):
|
||||
dlgOpt = QFileDialog.Options()
|
||||
dlgOpt |= QFileDialog.DontUseNativeDialog
|
||||
savePath, _ = QFileDialog.getSaveFileName(
|
||||
self, self.tr("Save Document As"), savePath, options=dlgOpt
|
||||
self, self.tr("Save Data As"), savePath, options=dlgOpt
|
||||
)
|
||||
if not savePath:
|
||||
return False
|
||||
|
||||
+1
-1
@@ -290,7 +290,7 @@ class GuiMain(QMainWindow):
|
||||
self.openProject(self.mainConf.cmdOpen)
|
||||
|
||||
logger.debug("novelWriter is ready ...")
|
||||
self.setStatus("novelWriter is ready ...")
|
||||
self.setStatus(self.tr("novelWriter is ready ..."))
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from nw.common import (
|
||||
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
|
||||
fuzzyTime, checkHandle, formatTimeStamp, formatTime, hexToInt,
|
||||
makeFileNameSafe, isHandle, isTitleTag, isItemClass, isItemType,
|
||||
isItemLayout
|
||||
isItemLayout, numberToRoman
|
||||
)
|
||||
from tools import cmpList
|
||||
|
||||
@@ -325,3 +325,30 @@ def testBaseCommon_MakeFileNameSafe():
|
||||
assert makeFileNameSafe("aaaa bbbb") == "aaaa bbbb"
|
||||
|
||||
# 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 nw.core.project import NWProject
|
||||
from nw.core.index import NWIndex
|
||||
from nw.core.index import NWIndex, countWords
|
||||
from nw.constants import nwItemClass, nwItemLayout
|
||||
|
||||
@pytest.mark.core
|
||||
@@ -1161,3 +1161,34 @@ def testCoreIndex_CheckTextCounts(dummyGUI):
|
||||
theIndex._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
|
||||
writeFile(rName, (
|
||||
"<?xml version='1.0' encoding='utf-8'?>\n"
|
||||
"<?xml version='0.0' encoding='utf-8'?>\n"
|
||||
"<novelWriterXML "
|
||||
"appVersion=\"1.0\" "
|
||||
"hexVersion=\"0x01000000\" "
|
||||
@@ -421,25 +421,6 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
|
||||
assert theProject.openProject(nwMinimal) is True
|
||||
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
|
||||
os.unlink(rName)
|
||||
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