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"?>
<!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"
}
}
+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/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
View File
@@ -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
View File
@@ -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")
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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 20182021, 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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
-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
# ===============
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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -3
View File
@@ -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:
+1 -1
View File
@@ -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__)
+7 -4
View File
@@ -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
View File
@@ -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]
+1 -1
View File
@@ -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
View File
@@ -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
+28 -1
View File
@@ -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
+32 -1
View File
@@ -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
+1 -20
View File
@@ -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)
-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