tag
@@ -451,7 +458,7 @@ class NWProject():
if not nwxRoot == "novelWriterXML":
self.makeAlert(
- "Project file does not appear to be a novelWriterXML file.",
+ self.tr("Project file does not appear to be a novelWriterXML file."),
nwAlert.ERROR
)
self.clearProject()
@@ -470,24 +477,23 @@ class NWProject():
# read the file. Introduced in version 0.10.
if fileVersion == "1.0":
- msgYes = self.theParent.askQuestion("Old Project Version", (
- "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?
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."
- ))
+ msgYes = self.theParent.askQuestion(self.tr("Old Project Version"), (
+ "%s
%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((
- "Unknown or unsupported novelWriter project file format. "
- "The project cannot be opened by this version of novelWriter. "
- "The file was saved with novelWriter version {vers:s}."
- ).format(
- vers = appVersion,
+ self.tr("Unknown or unsupported novelWriter project file format. "
+ "The project cannot be opened by this version of novelWriter. "
+ "The file was saved with novelWriter version {0}.").format(appVersion)
), nwAlert.ERROR)
self.clearProject()
return False
@@ -496,13 +502,14 @@ class NWProject():
# =========================
if hexToInt(hexVersion) > hexToInt(nw.__hexversion__):
- msgYes = self.theParent.askQuestion("Version Conflict", (
- "This project was saved by a newer version of novelWriter, version %s. "
- "This is version %s. 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?"
- ) % (
- appVersion, nw.__version__
+ msgYes = self.theParent.askQuestion(self.tr("Version Conflict"), (
+ self.tr("This project was saved by a newer version of novelWriter, version "
+ "{new_version}. This is version {version}. 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(
+ new_version = appVersion,
+ version = nw.__version__
))
if not msgYes:
self.clearProject()
@@ -602,7 +609,9 @@ class NWProject():
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
self.mainConf.saveRecentCache()
- self.theParent.setStatus("Opened Project: %s" % self.projName)
+ self.theParent.setStatus(self.tr("{0}: {1}").format(
+ self.tr("Opened Project"),
+ self.projName))
self._scanProjectFolder()
@@ -623,7 +632,7 @@ class NWProject():
"""
if self.projPath is None:
self.makeAlert(
- "Project path not set, cannot save project.", nwAlert.ERROR
+ self.tr("Project path not set, cannot save project."), nwAlert.ERROR
)
return False
@@ -702,7 +711,7 @@ class NWProject():
xml_declaration = True
))
except Exception as e:
- self.makeAlert(["Failed to save project.", str(e)], nwAlert.ERROR)
+ self.makeAlert([self.tr("Failed to save project."), str(e)], nwAlert.ERROR)
return False
# If we're here, the file was successfully saved,
@@ -721,7 +730,9 @@ class NWProject():
self.mainConf.saveRecentCache()
self._writeLockFile()
- self.theParent.setStatus("Saved Project: %s" % self.projName)
+ self.theParent.setStatus(self.tr("{0}: {1}").format(
+ self.tr("Saved Project"),
+ self.projName))
self.setProjectChanged(False)
return True
@@ -773,26 +784,26 @@ class NWProject():
return False
logger.info("Backing up project")
- self.theParent.setStatus("Backing up project ...")
+ self.theParent.setStatus(self.tr("Backing up project ..."))
if self.mainConf.backupPath is None or self.mainConf.backupPath == "":
self.theParent.makeAlert((
- "Cannot backup project because no backup path is set. "
- "Please set a valid backup location in Tools > Preferences."
+ self.tr("Cannot backup project because no backup path is set. "
+ "Please set a valid backup location in Tools > Preferences.")
), nwAlert.ERROR)
return False
if self.projName is None or self.projName == "":
self.theParent.makeAlert((
- "Cannot backup project because no project name is set. "
- "Please set a Working Title in Project > Project Settings."
+ self.tr("Cannot backup project because no project name is set. "
+ "Please set a Working Title in Project > Project Settings.")
), nwAlert.ERROR)
return False
if not os.path.isdir(self.mainConf.backupPath):
self.theParent.makeAlert((
- "Cannot backup project because the backup path does not exist. "
- "Please set a valid backup location in Tools > Preferences."
+ self.tr("Cannot backup project because the backup path does not exist. "
+ "Please set a valid backup location in Tools > Preferences.")
), nwAlert.ERROR)
return False
@@ -804,20 +815,20 @@ class NWProject():
logger.debug("Created folder %s" % baseDir)
except Exception as e:
self.theParent.makeAlert(
- ["Could not create backup folder.", str(e)],
+ [self.tr("Could not create backup folder."), str(e)],
nwAlert.ERROR
)
return False
if os.path.commonpath([self.projPath, baseDir]) == self.projPath:
self.theParent.makeAlert((
- "Cannot backup project because the backup path is within the "
- "project folder to be backed up. Please choose a different "
- "backup path in Tools > Preferences."
+ self.tr("Cannot backup project because the backup path is within the "
+ "project folder to be backed up. Please choose a different "
+ "backup path in Tools > Preferences.")
), nwAlert.ERROR)
return False
- archName = "Backup from %s" % formatTimeStamp(time(), fileSafe=True)
+ archName = self.tr("Backup from {0}").format(formatTimeStamp(time(), fileSafe=True))
baseName = os.path.join(baseDir, archName)
try:
@@ -827,18 +838,19 @@ class NWProject():
logger.info("Backup written to: %s" % archName)
if doNotify:
self.theParent.makeAlert(
- "Backup archive file written to: %s.zip" % os.path.join(cleanName, archName),
+ self.tr("Backup archive file written to: {0}").format(
+ f"{os.path.join(cleanName, archName)}.zip"),
nwAlert.INFO
)
except Exception as e:
self.theParent.makeAlert(
- ["Could not write backup archive.", str(e)],
+ [self.tr("Could not write backup archive."), str(e)],
nwAlert.ERROR
)
return False
- self.theParent.setStatus("Project backed up to '%s.zip'" % baseName)
+ self.theParent.setStatus(self.tr("Project backed up to '{0}'").format(f"{baseName}.zip"))
return True
@@ -853,8 +865,9 @@ class NWProject():
logger.error("No project path set for the example project")
return False
- srcSample = os.path.abspath(os.path.join(self.mainConf.appRoot, "sample"))
- pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip")
+ srcSample = os.path.abspath(os.path.join(self.mainConf.appRoot,
+ self.tr("sample")))
+ pkgSample = os.path.join(self.mainConf.assetPath, "%s.zip" % self.tr("sample"))
isSuccess = False
if os.path.isfile(pkgSample):
@@ -865,7 +878,7 @@ class NWProject():
isSuccess = True
except Exception as e:
self.makeAlert(
- ["Failed to create a new example project.", str(e)], nwAlert.ERROR
+ [self.tr("Failed to create a new example project."), str(e)], nwAlert.ERROR
)
elif os.path.isdir(srcSample):
@@ -876,8 +889,8 @@ class NWProject():
dstProj = os.path.join(projPath, nwFiles.PROJ_FILE)
shutil.copyfile(srcProj, dstProj)
- srcContent = os.path.join(srcSample, "content")
- dstContent = os.path.join(projPath, "content")
+ srcContent = os.path.join(srcSample, self.tr("content"))
+ dstContent = os.path.join(projPath, self.tr("content"))
for srcFile in os.listdir(srcContent):
srcDoc = os.path.join(srcContent, srcFile)
dstDoc = os.path.join(dstContent, srcFile)
@@ -887,13 +900,13 @@ class NWProject():
except Exception as e:
self.makeAlert(
- ["Failed to create a new example project.", str(e)], nwAlert.ERROR
+ [self.tr("Failed to create a new example project."), str(e)], nwAlert.ERROR
)
else:
self.makeAlert((
- "Failed to create a new example project. Could not find the "
- "necessary files. They seem to be missing from this installation."
+ self.tr("Failed to create a new example project. Could not find the "
+ "necessary files. They seem to be missing from this installation.")
), nwAlert.ERROR)
if isSuccess:
@@ -925,15 +938,15 @@ class NWProject():
logger.debug("Created folder %s" % projPath)
except Exception as e:
self.theParent.makeAlert((
- ["Could not create new project folder.", str(e)]
+ [self.tr("Could not create new project folder."), str(e)]
), nwAlert.ERROR)
return False
if os.path.isdir(projPath):
if os.listdir(self.projPath):
self.theParent.makeAlert((
- "New project folder is not empty. "
- "Each project requires a dedicated project folder."
+ self.tr("New project folder is not empty. "
+ "Each project requires a dedicated project folder.")
), nwAlert.ERROR)
return False
@@ -982,15 +995,15 @@ class NWProject():
if doBackup:
if not os.path.isdir(self.mainConf.backupPath):
self.theParent.makeAlert((
- "You must set a valid backup path in preferences to use "
- "the automatic project backup feature."
+ self.tr("You must set a valid backup path in preferences to use "
+ "the automatic project backup feature.")
), nwAlert.WARN)
return False
if self.projName == "":
self.theParent.makeAlert((
- "You must set a valid project name in project settings to "
- "use the automatic project backup feature."
+ self.tr("You must set a valid project name in project settings to "
+ "use the automatic project backup feature.")
), nwAlert.WARN)
return False
@@ -1330,7 +1343,7 @@ class NWProject():
# Report status
if len(orphanFiles) > 0:
self.makeAlert(
- "Found %d orphaned file(s) in project folder." % len(orphanFiles),
+ self.tr("Found {0} orphaned file(s) in project folder.").format(len(orphanFiles)),
nwAlert.WARN
)
else:
@@ -1352,10 +1365,12 @@ class NWProject():
oName, oParent, oClass, oLayout = aDoc.getMeta()
if oName:
- oName = "Recovered: %s" % oName.lstrip("Recovered: ")
+ oName = self.tr("{0}: {1}").format(
+ self.tr("Recovered"),
+ oName.lstrip(self.tr("{0}: ").format(self.tr("Recovered"))))
else:
nOrph += 1
- oName = "Recovered File %d" % nOrph
+ oName = self.tr("Recovered File {0}").format(nOrph)
# Recover file meta data
if oClass is None:
@@ -1383,8 +1398,8 @@ class NWProject():
if noWhere:
self.makeAlert((
- "One or more orphaned files could not be added back into the "
- "project. Make sure at least a Novel root folder exists."
+ self.tr("One or more orphaned files could not be added back into the "
+ "project. Make sure at least a Novel root folder exists.")
), nwAlert.WARN)
return True
@@ -1403,9 +1418,13 @@ class NWProject():
if not isFile:
# It's a new file, so add a header
if self.lastWCount > 0:
- outFile.write("# Offset %d\n" % self.lastWCount)
+ outFile.write("# %s\n" % self.tr("Offset {0}").format(self.lastWCount))
outFile.write("# %-17s %-19s %8s %8s %8s\n" % (
- "Start Time", "End Time", "Novel", "Notes", "Idle"
+ self.tr("Start Time"),
+ self.tr("End Time"),
+ self.tr("Novel"),
+ self.tr("Notes"),
+ self.tr("Idle"),
))
outFile.write("%-19s %-19s %8d %8d %8d\n" % (
@@ -1432,7 +1451,7 @@ class NWProject():
"""
theData = os.path.join(self.projPath, theFolder)
if not os.path.isdir(theData):
- errList.append("Not a folder: %s" % theData)
+ errList.append(self.tr("Not a folder: {0}").format(theData))
return errList
logger.info("Old data folder %s found" % theFolder)
@@ -1452,10 +1471,10 @@ class NWProject():
newPath = os.path.join(self.projContent, tHandle+".nwd")
try:
os.rename(theFile, newPath)
- logger.info("Moved file: %s" % theFile)
- logger.info("New location: %s" % newPath)
+ logger.info(self.tr("{0}: {1}").format(self.tr("Moved file"), theFile))
+ logger.info(self.tr("{0}: {1}").format(self.tr("New location"), newPath))
except Exception:
- errList.append("Could not move: %s" % theFile)
+ errList.append(self.tr("{0}: {1}").format(self.tr("Could not move"), theFile))
logger.error("Could not move: %s" % theFile)
nw.logException()
@@ -1464,7 +1483,8 @@ class NWProject():
os.unlink(theFile)
logger.info("Deleted file: %s" % theFile)
except Exception:
- errList.append("Could not delete: %s" % theFile)
+ errList.append(self.tr("{0}: {1}").format(
+ self.tr("Could not delete"), theFile))
logger.error("Could not delete: %s" % theFile)
nw.logException()
@@ -1479,7 +1499,7 @@ class NWProject():
os.rmdir(theData)
logger.info("Removed folder: %s" % theFolder)
except Exception:
- errList.append("Failed to remove: %s" % theFolder)
+ errList.append(self.tr("{0}: {1}").format(self.tr("Failed to remove"), theFolder))
logger.error("Failed to remove: %s" % theFolder)
nw.logException()
@@ -1489,9 +1509,9 @@ class NWProject():
"""Move an item that doesn't belong in the project folder to
a junk folder.
"""
- theJunk = os.path.join(self.projPath, "junk")
+ theJunk = os.path.join(self.projPath, self.tr("junk"))
if not self._checkFolder(theJunk):
- return "Could not make folder: %s" % theJunk
+ return self.tr("{0}: {1}").format(self.tr("Could not make folder"), theJunk)
theSrc = os.path.join(theDir, theItem)
theDst = os.path.join(theJunk, theItem)
@@ -1502,7 +1522,7 @@ class NWProject():
except Exception:
logger.error("Could not move item %s to junk." % theSrc)
nw.logException()
- return "Could not move item %s to junk." % theSrc
+ return self.tr("Could not move item {0} to junk.").format(theSrc)
return ""
diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py
index 9d22afcd..4aeb9841 100644
--- a/nw/core/spellcheck.py
+++ b/nw/core/spellcheck.py
@@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from PyQt5.QtCore import QCoreApplication
import nw
import logging
import os
@@ -94,7 +95,7 @@ class NWSpellCheck():
"""Translate a language tag to something more user friendly.
"""
spBits = spTag.split("_")
- spLang = isoLanguage.ISO_639_1.get(spBits[0], spBits[0])
+ spLang = QCoreApplication.translate("ISO", isoLanguage.ISO_639_1.get(spBits[0], spBits[0]))
if len(spBits) > 1:
spLang += " (%s)" % spBits[1]
return spLang
@@ -332,7 +333,9 @@ class NWSpellSimple(NWSpellCheck):
if fExt != ".dict":
continue
- spName = "%s [%s]" % (self.expandLanguage(fRoot), nwConst.SP_INTERNAL)
+ spName = "%s [%s]" % (
+ self.expandLanguage(fRoot),
+ QCoreApplication.translate("Constant", nwConst.SP_INTERNAL))
retList.append((fRoot, spName))
return retList
@@ -341,6 +344,6 @@ class NWSpellSimple(NWSpellCheck):
"""Return the tag and provider of the currently loaded
dictionary.
"""
- return self.theLang, nwConst.SP_INTERNAL
+ return self.theLang, QCoreApplication.translate("Constant", nwConst.SP_INTERNAL)
# END Class NWSpellSimple
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index 67ebcc0f..e8500168 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -406,9 +406,15 @@ class ToHtml(Tokenizer):
"""Apply HTML formatting to synopsis.
"""
if self.genMode == self.M_PREVIEW:
- return "\n" % tText
+ return "\n" % (
+ self.tr("Synopsis"),
+ tText
+ )
else:
- return "Synopsis: %s
\n" % tText
+ return "%s: %s
\n" % (
+ self.tr("Synopsis"),
+ tText
+ )
def _formatComments(self, tText):
"""Apply HTML formatting to comments.
@@ -416,7 +422,7 @@ class ToHtml(Tokenizer):
if self.genMode == self.M_PREVIEW:
return "\n" % tText
else:
- return "\n" % tText
+ return "\n" % (self.tr("Comment"), tText)
def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords.
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index 3e0474ae..27136879 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -24,11 +24,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from functools import partial
import logging
import re
from operator import itemgetter
-from PyQt5.QtCore import QRegularExpression
+from PyQt5.QtCore import QCoreApplication, QRegularExpression
from nw.core.document import NWDoc
from nw.core.tools import numberToWord, numberToRoman
@@ -141,6 +142,8 @@ class Tokenizer():
# Error Handling
self.errData = []
+ self.tr = partial(QCoreApplication.translate, self.__class__.__name__)
+
return
##
@@ -249,7 +252,7 @@ class Tokenizer():
if theItem.itemType != nwItemType.ROOT:
return False
- theTitle = "Notes: %s" % theItem.itemName
+ theTitle = self.tr("{0}: {1}").format(self.tr("Notes"), theItem.itemName)
self.theTokens = []
self.theTokens.append((
self.T_TITLE, 0, theTitle, None, self.A_PBB | self.A_CENTRE
@@ -278,10 +281,11 @@ class Tokenizer():
docSize = len(self.theText)
if docSize > nwConst.MAX_DOCSIZE:
- errVal = "Document '%s' is too big (%.2f MB). Skipping." % (
- self.theItem.itemName, docSize/1.0e6
+ errVal = self.tr("Document '{doc_name}' is too big ({doc_size}). Skipping.").format(
+ doc_name = self.theItem.itemName,
+ doc_size = f"{docSize/1.0e6:.2f} MB"
)
- self.theText = "# ERROR\n\n%s\n\n" % errVal
+ self.theText = "# %s\n\n%s\n\n" % (self.tr("ERROR"), errVal)
self.errData.append(errVal)
self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
diff --git a/nw/core/tools.py b/nw/core/tools.py
index 44b4c865..91b59c93 100644
--- a/nw/core/tools.py
+++ b/nw/core/tools.py
@@ -127,7 +127,7 @@ def numberToWord(numVal, theLanguage):
"""Wrapper for converting numbers to words for chapter headings.
"""
numWord = ""
- if theLanguage == "en":
+ if theLanguage == "en": # TODO: I18N
numWord = _numberToWordEN(numVal)
else:
numWord = _numberToWordEN(numVal)
diff --git a/nw/core/tree.py b/nw/core/tree.py
index 5fdc55ba..d614b4b9 100644
--- a/nw/core/tree.py
+++ b/nw/core/tree.py
@@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from functools import partial
import nw
import logging
import os
@@ -32,6 +33,8 @@ from lxml import etree
from hashlib import sha256
from time import time
+from PyQt5.QtCore import QCoreApplication
+
from nw.core.item import NWItem
from nw.common import checkHandle
from nw.constants import (
@@ -79,6 +82,8 @@ class NWTree():
self._handleSeed = None # Used for generating handles for testing
+ self.tr = partial(QCoreApplication.translate, self.__class__.__name__)
+
return
##
@@ -195,11 +200,14 @@ class NWTree():
tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT)
with open(tocText, mode="w", encoding="utf8") as outFile:
outFile.write("\n")
- outFile.write("Table of Contents\n")
+ outFile.write("%s\n" % self.tr("Table of Contents"))
outFile.write("=================\n")
outFile.write("\n")
outFile.write("%-25s %-9s %-10s %s\n" % (
- "File Name", "Class", "Layout", "Document Label"
+ self.tr("File Name"),
+ self.tr("Class"),
+ self.tr("Layout"),
+ self.tr("Document Label"),
))
outFile.write("-"*tocLen + "\n")
outFile.write("\n".join(tocList))
diff --git a/nw/error.py b/nw/error.py
index 998b7347..c29e3eb6 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -68,6 +68,7 @@ class NWErrorMessage(QDialog):
self.msgBody.setReadOnly(True)
self.btnBox = QDialogButtonBox(QDialogButtonBox.Close)
+ self.btnBox.button(QDialogButtonBox.Close).setText(self.tr("Close"))
self.btnBox.rejected.connect(self._doClose)
# Assemble
diff --git a/nw/gui/about.py b/nw/gui/about.py
index d584cb25..9e90c5f8 100644
--- a/nw/gui/about.py
+++ b/nw/gui/about.py
@@ -55,14 +55,14 @@ class GuiAbout(QDialog):
self.innerBox = QHBoxLayout()
self.innerBox.setSpacing(self.mainConf.pxInt(16))
- self.setWindowTitle("About novelWriter")
+ self.setWindowTitle(self.tr("About novelWriter"))
self.setMinimumWidth(self.mainConf.pxInt(650))
self.setMinimumHeight(self.mainConf.pxInt(600))
nPx = self.mainConf.pxInt(96)
self.nwIcon = QLabel()
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
- self.lblName = QLabel("novelWriter")
+ self.lblName = QLabel("%s" % self.tr("novelWriter"))
self.lblVers = QLabel("v%s" % nw.__version__)
self.lblDate = QLabel(datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x"))
@@ -90,13 +90,14 @@ class GuiAbout(QDialog):
# Main Tab Area
self.tabBox = QTabWidget()
- self.tabBox.addTab(self.pageAbout, "About")
- self.tabBox.addTab(self.pageNotes, "Release")
- self.tabBox.addTab(self.pageLicense, "License")
+ self.tabBox.addTab(self.pageAbout, self.tr("About"))
+ self.tabBox.addTab(self.pageNotes, self.tr("Release"))
+ self.tabBox.addTab(self.pageLicense, self.tr("License"))
self.innerBox.addWidget(self.tabBox)
# OK Button
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
+ self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK"))
self.buttonBox.accepted.connect(self._doClose)
self.outerBox.addLayout(self.innerBox)
@@ -132,28 +133,30 @@ class GuiAbout(QDialog):
"""Generate the content for the About page.
"""
listPrefix = " • "
- aboutMsg = (
- "About novelWriter
"
- "{copyright:s}.
"
- "Website: {domain:s}
"
- "novelWriter is a markdown-like text editor designed for "
- "organising and writing novels. It is written in Python 3 with a "
- "Qt5 GUI, using PyQt5.
"
- "novelWriter 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.
"
- "novelWriter 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 License tab for the full license text, or visit the "
- "GNU website at "
- "GPL v3.0 "
- "for more details.
"
- "Credits
"
- "{credits:s}
"
- ).format(
+ aboutMsg = "".join([
+ "%s
" % self.tr("About novelWriter"),
+ "{copyright:s}.
",
+ "%s
" % (self.tr("{0}: {1}").format(
+ self.tr("Website"),
+ "{domain:s}"
+ )),
+ "%s
" % self.tr("novelWriter is a markdown-like text editor designed for "
+ "organising and writing novels. It is written in Python 3 with "
+ "a Qt5 GUI, using PyQt5."),
+ "%s
" % self.tr("novelWriter 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."),
+ "%s
" % self.tr("novelWriter 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."),
+ "%s
" % (self.tr("See the License tab for the full license text, or visit the "
+ "GNU website at {0} for more details.").format(
+ "GPL v3.0")),
+ "%s
" % self.tr("Credits"),
+ "{credits:s}
",
+ ]).format(
copyright = nw.__copyright__,
website = nw.__url__,
domain = nw.__domain__,
@@ -163,50 +166,52 @@ class GuiAbout(QDialog):
theTheme = self.theParent.theTheme
theIcons = self.theParent.theTheme.theIcons
if theTheme.themeName:
- aboutMsg += (
- "Theme: {name:s}
"
- ""
- "Author: {author:s}
"
- "Credit: {credit:s}
"
- "License: {license:s}"
+ aboutMsg += "".join([
+ ("
%s
" % self.tr("{0}: {1}").format(self.tr("Theme"), theTheme.themeName)),
+ "",
+ ("%s
" % self.tr("{0}: {1}").format(
+ self.tr("Author"), theTheme.themeAuthor)),
+ ("%s
" % self.tr("{0}: {1}").format(
+ self.tr("Credit"), theTheme.themeCredit)),
+ (self.tr("{0}: {1}").format(
+ self.tr("License"),
+ "{1}".format(
+ theTheme.themeLicenseUrl, theTheme.themeLicense)
+ )),
"
"
- ).format(
- name = theTheme.themeName,
- author = theTheme.themeAuthor,
- credit = theTheme.themeCredit,
- license = theTheme.themeLicense,
- lic_url = theTheme.themeLicenseUrl,
- )
+ ])
if theIcons.themeName:
- aboutMsg += (
- "Icons: {name:s}
"
- ""
- "Author: {author:s}
"
- "Credit: {credit:s}
"
- "License: {license:s}"
+ aboutMsg += "".join([
+ ("
%s
" % self.tr("{0}: {1}").format(self.tr("Icons"), theIcons.themeName)),
+ "",
+ ("%s
" % self.tr("{0}: {1}").format(
+ self.tr("Author"), theIcons.themeAuthor)),
+ ("%s
" % self.tr("{0}: {1}").format(
+ self.tr("Credit"), theIcons.themeCredit)),
+ (self.tr("{0}: {1}").format(
+ self.tr("License"),
+ "{1}".format(
+ theIcons.themeLicenseUrl, theIcons.themeLicense)
+ )),
"
"
- ).format(
- name = theIcons.themeName,
- author = theIcons.themeAuthor,
- credit = theIcons.themeCredit,
- license = theIcons.themeLicense,
- lic_url = theIcons.themeLicenseUrl,
- )
+ ])
if theTheme.syntaxName:
- aboutMsg += (
- "Syntax: {name:s}
"
- ""
- "Author: {author:s}
"
- "Credit: {credit:s}
"
- "License: {license:s}"
+ aboutMsg += "".join([
+ ("
%s
" % self.tr("{0}: {1}").format(
+ self.tr("Syntax"),
+ theTheme.syntaxName)),
+ "",
+ ("%s
" % self.tr("{0}: {1}").format(
+ self.tr("Author"), theTheme.syntaxAuthor)),
+ ("%s
" % self.tr("{0{0} {{1}").format(
+ self.tr("Credit"), theTheme.syntaxCredit)),
+ (self.tr("{0}: {1}").format(
+ self.tr("License"),
+ "{1}".format(
+ theTheme.syntaxLicenseUrl, theTheme.syntaxLicense)
+ )),
"
"
- ).format(
- name = theTheme.syntaxName,
- author = theTheme.syntaxAuthor,
- credit = theTheme.syntaxCredit,
- license = theTheme.syntaxLicense,
- lic_url = theTheme.syntaxLicenseUrl,
- )
+ ])
self.pageAbout.setHtml(aboutMsg)
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 3b76248f..33ba23d6 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -84,7 +84,7 @@ class GuiBuildNovel(QDialog):
self.htmlSize = 0 # Size of the html document
self.buildTime = 0 # The timestamp of the last build
- self.setWindowTitle("Build Novel Project")
+ self.setWindowTitle(self.tr("Build Novel Project"))
self.setMinimumWidth(self.mainConf.pxInt(700))
self.setMinimumHeight(self.mainConf.pxInt(600))
@@ -101,26 +101,26 @@ class GuiBuildNovel(QDialog):
# Title Formats
# =============
- self.titleGroup = QGroupBox("Title Formats for Novel Files", self)
+ self.titleGroup = QGroupBox(self.tr("Title Formats for Novel Files"), self)
self.titleForm = QGridLayout(self)
self.titleGroup.setLayout(self.titleForm)
- fmtHelp = (
- r"Formatting Codes:
"
- r"%title% for the title as set in the document
"
- r"%ch% for chapter number (1, 2, 3)
"
- r"%chw% for chapter number as a word (one, two)
"
- r"%chI% for chapter number in upper case Roman
"
- r"%chi% for chapter number in lower case Roman
"
- r"%sc% for scene number within chapter
"
- r"%sca% for scene number within novel"
+ fmtHelp = "
".join(
+ "%s" % self.tr("{0}:").format("Formatting Codes"),
+ self.tr("{0} for the title as set in the document").format(r"%title%"),
+ self.tr("{0} for chapter number (1, 2, 3)").format(r"%ch%"),
+ self.tr("{0} for chapter number as a word (one, two)").format(r"%chw%"),
+ self.tr("{0} for chapter number in upper case Roman").format(r"%chI%"),
+ self.tr("{0} for chapter number in lower case Roman").format(r"%chi%"),
+ self.tr("{0} for scene number within chapter").format(r"%sc%"),
+ self.tr("{0} for scene number within novel").format(r"%sca%"),
)
fmtScHelp = (
- r"
"
- r"Leave blank to skip this heading, or set to a static text, like "
- r"for instance '* * *', to make a separator. The separator will "
- r"be centred automatically and only appear between sections of "
- r"the same type."
+ "
%s" %
+ self.tr("Leave blank to skip this heading, or set to a static text, like "
+ "for instance '{0}', to make a separator. The separator will "
+ "be centred automatically and only appear between sections of "
+ "the same type.").format("* * *")
)
xFmt = self.mainConf.pxInt(100)
@@ -176,11 +176,11 @@ class GuiBuildNovel(QDialog):
self.boxSection = QHBoxLayout()
self.boxSection.addWidget(self.fmtSection)
- titleLabel = QLabel("Title")
- chapterLabel = QLabel("Chapter")
- unnumbLabel = QLabel("Unnumbered")
- sceneLabel = QLabel("Scene")
- sectionLabel = QLabel("Section")
+ titleLabel = QLabel(self.tr("Title"))
+ chapterLabel = QLabel(self.tr("Chapter"))
+ unnumbLabel = QLabel(self.tr("Unnumbered"))
+ sceneLabel = QLabel(self.tr("Scene"))
+ sectionLabel = QLabel(self.tr("Section"))
self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight)
@@ -199,7 +199,7 @@ class GuiBuildNovel(QDialog):
# Font Options
# ============
- self.fontGroup = QGroupBox("Font Options", self)
+ self.fontGroup = QGroupBox(self.tr("Font Options"), self)
self.fontForm = QGridLayout(self)
self.fontGroup.setLayout(self.fontForm)
@@ -237,11 +237,11 @@ class GuiBuildNovel(QDialog):
self.boxFont = QHBoxLayout()
self.boxFont.addWidget(self.textFont)
- fontFamilyLabel = QLabel("Font family")
- fontSizeLabel = QLabel("Font size")
- lineHeightLabel = QLabel("Line height")
- justifyLabel = QLabel("Justify text")
- stylingLabel = QLabel("Disable styling")
+ fontFamilyLabel = QLabel(self.tr("Font family"))
+ fontSizeLabel = QLabel(self.tr("Font size"))
+ lineHeightLabel = QLabel(self.tr("Line height"))
+ justifyLabel = QLabel(self.tr("Justify text"))
+ stylingLabel = QLabel(self.tr("Disable styling"))
self.fontForm.addWidget(fontFamilyLabel, 0, 0, 1, 1, Qt.AlignLeft)
self.fontForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight)
@@ -283,7 +283,7 @@ class GuiBuildNovel(QDialog):
# Include Options
# ===============
- self.textGroup = QGroupBox("Include Options", self)
+ self.textGroup = QGroupBox(self.tr("Include Options"), self)
self.textForm = QGridLayout(self)
self.textGroup.setLayout(self.textForm)
@@ -307,10 +307,10 @@ class GuiBuildNovel(QDialog):
self.optState.getBool("GuiBuildNovel", "incBodyText", True)
)
- synopsisLabel = QLabel("Include synopsis")
- commentsLabel = QLabel("Include comments")
- keywordsLabel = QLabel("Include keywords")
- bodyLabel = QLabel("Include body text")
+ synopsisLabel = QLabel(self.tr("Include synopsis"))
+ commentsLabel = QLabel(self.tr("Include comments"))
+ keywordsLabel = QLabel(self.tr("Include keywords"))
+ bodyLabel = QLabel(self.tr("Include body text"))
self.textForm.addWidget(synopsisLabel, 0, 0, 1, 1, Qt.AlignLeft)
self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight)
@@ -327,37 +327,37 @@ class GuiBuildNovel(QDialog):
# File Filter Options
# ===================
- self.fileGroup = QGroupBox("File Filter Options", self)
+ self.fileGroup = QGroupBox(self.tr("File Filter Options"), self)
self.fileForm = QGridLayout(self)
self.fileGroup.setLayout(self.fileForm)
self.novelFiles = QSwitch(width=wS, height=hS)
self.novelFiles.setToolTip(
- "Include files with layouts 'Book', 'Page', 'Partition', "
- "'Chapter', 'Unnumbered', and 'Scene'."
+ self.tr("Include files with layouts 'Book', 'Page', 'Partition', "
+ "'Chapter', 'Unnumbered', and 'Scene'.")
)
self.novelFiles.setChecked(
self.optState.getBool("GuiBuildNovel", "addNovel", True)
)
self.noteFiles = QSwitch(width=wS, height=hS)
- self.noteFiles.setToolTip("Include files with layout 'Note'.")
+ self.noteFiles.setToolTip(self.tr("Include files with layout 'Note'."))
self.noteFiles.setChecked(
self.optState.getBool("GuiBuildNovel", "addNotes", False)
)
self.ignoreFlag = QSwitch(width=wS, height=hS)
self.ignoreFlag.setToolTip(
- "Ignore the 'Include when building project' setting and include "
- "all files in the output."
+ self.tr("Ignore the 'Include when building project' setting and include "
+ "all files in the output.")
)
self.ignoreFlag.setChecked(
self.optState.getBool("GuiBuildNovel", "ignoreFlag", False)
)
- novelLabel = QLabel("Include novel files")
- notesLabel = QLabel("Include note files")
- exportLabel = QLabel("Ignore export flag")
+ novelLabel = QLabel(self.tr("Include novel files"))
+ notesLabel = QLabel(self.tr("Include note files"))
+ exportLabel = QLabel(self.tr("Ignore export flag"))
self.fileForm.addWidget(novelLabel, 0, 0, 1, 1, Qt.AlignLeft)
self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight)
@@ -372,7 +372,7 @@ class GuiBuildNovel(QDialog):
# Export Options
# ==============
- self.exportGroup = QGroupBox("Export Options", self)
+ self.exportGroup = QGroupBox(self.tr("Export Options"), self)
self.exportForm = QGridLayout(self)
self.exportGroup.setLayout(self.exportForm)
@@ -386,8 +386,8 @@ class GuiBuildNovel(QDialog):
self.optState.getBool("GuiBuildNovel", "replaceUCode", False)
)
- tabsLabel = QLabel("Replace tabs with spaces")
- uCodeLabel = QLabel("Replace Unicode in HTML")
+ tabsLabel = QLabel(self.tr("Replace tabs with spaces"))
+ uCodeLabel = QLabel(self.tr("Replace Unicode in HTML"))
self.exportForm.addWidget(tabsLabel, 0, 0, 1, 1, Qt.AlignLeft)
self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight)
@@ -402,7 +402,7 @@ class GuiBuildNovel(QDialog):
self.buildProgress = QProgressBar()
- self.buildNovel = QPushButton("Build Preview")
+ self.buildNovel = QPushButton(self.tr("Build Preview"))
self.buildNovel.clicked.connect(self._buildPreview)
# Action Buttons
@@ -413,14 +413,14 @@ class GuiBuildNovel(QDialog):
# Printing
self.printMenu = QMenu(self)
- self.btnPrint = QPushButton("Print")
+ self.btnPrint = QPushButton(self.tr("Print"))
self.btnPrint.setMenu(self.printMenu)
- self.printSend = QAction("Print Preview", self)
+ self.printSend = QAction(self.tr("Print Preview"), self)
self.printSend.triggered.connect(self._printDocument)
self.printMenu.addAction(self.printSend)
- self.printFile = QAction("Print to PDF", self)
+ self.printFile = QAction(self.tr("Print to PDF"), self)
self.printFile.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
self.printMenu.addAction(self.printFile)
@@ -430,39 +430,46 @@ class GuiBuildNovel(QDialog):
self.btnSave = QPushButton("Save As")
self.btnSave.setMenu(self.saveMenu)
- self.saveODT = QAction("Open Document (.odt)", self)
+ self.saveODT = QAction(self.tr("{0} ({1})").format(self.tr("Open Document"), ".odt"), self)
self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT))
self.saveMenu.addAction(self.saveODT)
- self.saveFODT = QAction("Flat Open Document (.fodt)", self)
+ self.saveFODT = QAction(
+ self.tr("{0} ({1})").format(self.tr("Flat Open Document"), ".fodt"), self)
self.saveFODT.triggered.connect(lambda: self._saveDocument(self.FMT_FODT))
self.saveMenu.addAction(self.saveFODT)
- self.saveHTM = QAction("novelWriter HTML (.htm)", self)
+ self.saveHTM = QAction(self.tr("{0} ({1})").format(
+ self.tr("novelWriter HTML"), ".htm"), self)
self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM))
self.saveMenu.addAction(self.saveHTM)
- self.saveNWD = QAction("novelWriter Markdown (.nwd)", self)
+ self.saveNWD = QAction(self.tr("{0} ({1})").format(
+ self.tr("novelWriter Markdown"), ".nwd"), self)
self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD))
self.saveMenu.addAction(self.saveNWD)
- self.saveMD = QAction("Standard Markdown (.md)", self)
+ self.saveMD = QAction(
+ self.tr("{0} ({1})").format(self.tr("Standard Markdown"), ".md"), self)
self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
self.saveMenu.addAction(self.saveMD)
- self.saveGH = QAction("GitHub Markdown (.md)", self)
+ self.saveGH = QAction(
+ self.tr("{0} ({1})").format(self.tr("GitHub Markdown"), ".md"), self)
self.saveGH.triggered.connect(lambda: self._saveDocument(self.FMT_GH))
self.saveMenu.addAction(self.saveGH)
- self.saveJsonH = QAction("JSON + novelWriter HTML (.json)", self)
+ self.saveJsonH = QAction(self.tr("{0} ({1})").format(
+ self.tr("JSON + novelWriter HTML"), ".json"), self)
self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H))
self.saveMenu.addAction(self.saveJsonH)
- self.saveJsonM = QAction("JSON + novelWriters Markdown (.json)", self)
+ self.saveJsonM = QAction(self.tr("{0} ({1})").format(
+ self.tr("JSON + novelWriters Markdown"), ".json"), self)
self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M))
self.saveMenu.addAction(self.saveJsonM)
- self.btnClose = QPushButton("Close")
+ self.btnClose = QPushButton(self.tr("Close"))
self.btnClose.clicked.connect(self._doClose)
self.buttonBox.addWidget(self.btnSave)
@@ -567,7 +574,7 @@ class GuiBuildNovel(QDialog):
self.docView.setContent(self.htmlText, self.buildTime)
else:
self.docView.setText(
- "Failed to generate preview. The result is too big."
+ self.tr("Failed to generate preview. The result is too big.")
)
else:
@@ -736,10 +743,9 @@ class GuiBuildNovel(QDialog):
logger.debug("Built project in %.3f ms" % (1000*(tEnd - tStart)))
if bldObj.errData:
- self.theParent.makeAlert((
- "There were problems when building the project:"
- "
- %s"
- ) % "
- ".join(bldObj.errData), nwAlert.ERROR)
+ self.theParent.makeAlert("%s:
- %s" % (
+ self.tr("There were problems when building the project"),
+ "
- ".join(bldObj.errData)), nwAlert.ERROR)
return
@@ -796,39 +802,39 @@ class GuiBuildNovel(QDialog):
if theFmt == self.FMT_ODT:
fileExt = "odt"
- textFmt = "Open Document"
+ textFmt = self.tr("Open Document")
elif theFmt == self.FMT_FODT:
fileExt = "fodt"
- textFmt = "Flat Open Document"
+ textFmt = self.tr("Flat Open Document")
elif theFmt == self.FMT_HTM:
fileExt = "htm"
- textFmt = "Plain HTML"
+ textFmt = self.tr("Plain HTML")
elif theFmt == self.FMT_NWD:
fileExt = "nwd"
- textFmt = "novelWriter Markdown"
+ textFmt = self.tr("novelWriter Markdown")
elif theFmt == self.FMT_MD:
fileExt = "md"
- textFmt = "Standard Markdown"
+ textFmt = self.tr("Standard Markdown")
elif theFmt == self.FMT_GH:
fileExt = "md"
- textFmt = "GitHub Markdown"
+ textFmt = self.tr("GitHub Markdown")
elif theFmt == self.FMT_JSON_H:
fileExt = "json"
- textFmt = "JSON + novelWriter HTML"
+ textFmt = self.tr("JSON + novelWriter HTML")
elif theFmt == self.FMT_JSON_M:
fileExt = "json"
- textFmt = "JSON + novelWriter Markdown"
+ textFmt = self.tr("JSON + novelWriter Markdown")
elif theFmt == self.FMT_PDF:
fileExt = "pdf"
- textFmt = "PDF"
+ textFmt = self.tr("PDF")
else:
return False
@@ -848,7 +854,7 @@ class GuiBuildNovel(QDialog):
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
savePath, _ = QFileDialog.getSaveFileName(
- self, "Save Document As", savePath, options=dlgOpt
+ self, self.tr("Save Document As"), savePath, options=dlgOpt
)
if not savePath:
return False
@@ -985,20 +991,20 @@ class GuiBuildNovel(QDialog):
errMsg - str(e)
else:
- errMsg = "Unknown format"
+ errMsg = self.tr("Unknown format")
# Report to user
if wSuccess:
self.theParent.makeAlert(
- "%s file successfully written to:
%s" % (
- textFmt, savePath
+ "%s
%s" % (
+ self.tr("{0} file successfully written to:").format(textFmt),
+ savePath
), nwAlert.INFO
)
else:
self.theParent.makeAlert(
- "Failed to write %s file. %s" % (
- textFmt, errMsg
- ), nwAlert.ERROR
+ self.tr("Failed to write {0} file. {1}").format(
+ textFmt, errMsg), nwAlert.ERROR
)
return wSuccess
@@ -1194,9 +1200,9 @@ class GuiBuildNovelDocView(QTextBrowser):
self.qDocument = self.document()
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
self.setPlaceholderText(
- "This area will show the content of the document to be "
- "exported or printed. Press the \"Build Preview\" button "
- "to generate content."
+ self.tr("This area will show the content of the document to be "
+ "exported or printed. Press the \"Build Preview\" button "
+ "to generate content.")
)
theFont = QFont()
@@ -1227,7 +1233,8 @@ class GuiBuildNovelDocView(QTextBrowser):
fPx = int(1.1*self.theTheme.fontPixelSize)
- self.theTitle = QLabel("Build Time: Unknown", self)
+ self.theTitle = QLabel(self.tr("{0}: {1}".format(
+ self.tr("Build Time"), self.tr("Unknown"))), self)
self.theTitle.setIndent(0)
self.theTitle.setAutoFillBackground(True)
self.theTitle.setAlignment(Qt.AlignCenter)
@@ -1341,8 +1348,9 @@ class GuiBuildNovelDocView(QTextBrowser):
fuzzyTime(time() - self.buildTime)
)
else:
- strBuildTime = "Unknown"
- self.theTitle.setText("Build Time: %s" % strBuildTime)
+ strBuildTime = self.tr("Unknown")
+ self.theTitle.setText(self.tr("{0}: {1}").format(
+ self.tr("Build Time"), strBuildTime))
def _updateDocMargins(self):
"""Automatically adjust the header to fill the top of the
diff --git a/nw/gui/custom.py b/nw/gui/custom.py
index 303a5ae1..4b187f21 100644
--- a/nw/gui/custom.py
+++ b/nw/gui/custom.py
@@ -500,6 +500,8 @@ class QuotesDialog(QDialog):
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ 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)
diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py
index abafc6fb..146fe07e 100644
--- a/nw/gui/doceditor.py
+++ b/nw/gui/doceditor.py
@@ -36,7 +36,7 @@ import logging
from time import time
from PyQt5.QtCore import (
- Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
+ QCoreApplication, Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
QPointF, QObject, QRunnable, QPropertyAnimation
)
from PyQt5.QtGui import (
@@ -294,10 +294,14 @@ class GuiDocEditor(QTextEdit):
docSize = len(theDoc)
if docSize > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert((
- "The document you are trying to open is too big. "
- "The document size is %.2f\u202fMB. "
- "The maximum size allowed is %.2f\u202fMB."
- ) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
+ self.tr("The document you are trying to open is too big. "
+ "The document size is {doc_size}. "
+ "The maximum size allowed is {max_size}.").
+ format(
+ doc_size=self.tr("{0}\u202fMB").format(f"{docSize/1.0e6:.2f}"),
+ max_size=self.tr("{0}\u202fMB").format(f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}")
+ )
+ ), nwAlert.ERROR)
self.clearEditor()
return False
@@ -381,10 +385,14 @@ class GuiDocEditor(QTextEdit):
docSize = len(theText)
if docSize > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert((
- "The text you are trying to add is too big. "
- "The text size is %.2f\u202fMB. "
- "The maximum size allowed is %.2f\u202fMB."
- ) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
+ self.tr("The text you are trying to add is too big. "
+ "The text size is {text_size}. "
+ "The maximum size allowed is {max_size}.").
+ format(
+ text_size=self.tr("{0}\u202fMB").format(f"{docSize/1.0e6:.2f}"),
+ max_size=self.tr("{0}\u202fMB").format(f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}")
+ )
+ ), nwAlert.ERROR)
return False
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
@@ -594,7 +602,10 @@ class GuiDocEditor(QTextEdit):
aLang, aName = self.theDict.describeDict()
self.theParent.statusBar.setLanguage(
- aLang, "%s [%s]" % (self.mainConf.spellTool.title(), aName.title())
+ aLang,
+ self.tr("{0} [{1}]").format(
+ self.mainConf.spellTool.title(),
+ aName.title())
)
if not self.bigDoc:
@@ -644,7 +655,7 @@ class GuiDocEditor(QTextEdit):
logger.debug(
"Document highlighted in %.3f ms" % (1000*(afTime-bfTime))
)
- self.theParent.statusBar.showMessage("Spell check complete")
+ self.theParent.statusBar.showMessage(self.tr("Spell check complete"))
return True
@@ -743,11 +754,12 @@ class GuiDocEditor(QTextEdit):
return False
msgBox = QMessageBox()
- msgBox.information(self, "File Location", (
- "File details for the currently open file
"
- "Handle: {handle:s}
"
- "Location: {fileLoc:s}"
- ).format(
+ msgBox.information(self, self.tr("File Location"), "".join([
+ (self.tr("{0}
").format(self.tr("File details for the currently open file"))),
+ (self.tr("{0}
").format(
+ self.tr("{0}: {1}").format(self.tr("Handle"), "{handle:s}"))),
+ (self.tr("{0}: {1}").format(self.tr("Location"), "{fileLoc:s}"))
+ ]).format(
handle = self.theHandle,
fileLoc = str(self.nwDocument.getFileLocation())
))
@@ -934,9 +946,10 @@ class GuiDocEditor(QTextEdit):
if self.qDocument.characterCount() > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert((
- "The document has grown too big and you cannot add more text to it. "
- "The maximum size of a single novelWriter document is %.2f\u202fMB."
- ) % (nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
+ self.tr("The document has grown too big and you cannot add more text to it. "
+ "The maximum size of a single novelWriter document is {max_size}.").
+ format(max_size=self.tr("{0}\u202fMB").format(f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"))
+ ), nwAlert.ERROR)
self.undo()
return
@@ -966,21 +979,21 @@ class GuiDocEditor(QTextEdit):
# ===========================
if self._followTag(theCursor=posCursor, loadTag=False):
- mnuTag = QAction("Follow Tag", mnuContext)
+ mnuTag = QAction(self.tr("Follow Tag"), mnuContext)
mnuTag.triggered.connect(lambda: self._followTag(theCursor=posCursor))
mnuContext.addAction(mnuTag)
mnuContext.addSeparator()
if userSelection:
- mnuCut = QAction("Cut", mnuContext)
+ mnuCut = QAction(self.tr("Cut"), mnuContext)
mnuCut.triggered.connect(lambda: self.docAction(nwDocAction.CUT))
mnuContext.addAction(mnuCut)
- mnuCopy = QAction("Copy", mnuContext)
+ mnuCopy = QAction(self.tr("Copy"), mnuContext)
mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
mnuContext.addAction(mnuCopy)
- mnuPaste = QAction("Paste", mnuContext)
+ mnuPaste = QAction(self.tr("Paste"), mnuContext)
mnuPaste.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
mnuContext.addAction(mnuPaste)
@@ -989,17 +1002,17 @@ class GuiDocEditor(QTextEdit):
# Selections
# ==========
- mnuSelAll = QAction("Select All", mnuContext)
+ mnuSelAll = QAction(self.tr("Select All"), mnuContext)
mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
mnuContext.addAction(mnuSelAll)
- mnuSelWord = QAction("Select Word", mnuContext)
+ mnuSelWord = QAction(self.tr("Select Word"), mnuContext)
mnuSelWord.triggered.connect(
lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos)
)
mnuContext.addAction(mnuSelWord)
- mnuSelPara = QAction("Select Paragraph", mnuContext)
+ mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext)
mnuSelPara.triggered.connect(
lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos)
)
@@ -1025,7 +1038,7 @@ class GuiDocEditor(QTextEdit):
if spellCheck:
mnuContext.addSeparator()
- mnuHead = QAction("Spelling Suggestion(s)", mnuContext)
+ mnuHead = QAction(self.tr("Spelling Suggestion(s)"), mnuContext)
mnuContext.addAction(mnuHead)
theSuggest = self.theDict.suggestWords(theWord)[:15]
@@ -1037,11 +1050,12 @@ class GuiDocEditor(QTextEdit):
)
mnuContext.addAction(mnuWord)
else:
- mnuHead = QAction("%s No Suggestions" % nwUnicode.U_ENDASH, mnuContext)
+ mnuHead = QAction("%s %s" % (nwUnicode.U_ENDASH, self.tr("No Suggestions")),
+ mnuContext)
mnuContext.addAction(mnuHead)
mnuContext.addSeparator()
- mnuAdd = QAction("Add Word to Dictionary", mnuContext)
+ mnuAdd = QAction(self.tr("Add Word to Dictionary"), mnuContext)
mnuAdd.triggered.connect(lambda thePos: self._addWord(posCursor))
mnuContext.addAction(mnuAdd)
@@ -1317,7 +1331,8 @@ class GuiDocEditor(QTextEdit):
else:
self.theParent.makeAlert(
- "Please selection some text before calling replace quotes.", nwAlert.ERROR
+ self.tr("Please selection some text before calling replace quotes."),
+ nwAlert.ERROR
)
return
@@ -1817,12 +1832,12 @@ class GuiDocEditSearch(QFrame):
# ==========
self.searchBox = QLineEdit(self)
self.searchBox.setFont(boxFont)
- self.searchBox.setPlaceholderText("Search")
+ self.searchBox.setPlaceholderText(self.tr("Search"))
self.searchBox.returnPressed.connect(self._doSearch)
self.replaceBox = QLineEdit(self)
self.replaceBox.setFont(boxFont)
- self.replaceBox.setPlaceholderText("Replace")
+ self.replaceBox.setPlaceholderText(self.tr("Replace"))
self.replaceBox.returnPressed.connect(self._doReplace)
self.searchOpt = QToolBar(self)
@@ -1831,44 +1846,45 @@ class GuiDocEditSearch(QFrame):
self.searchOpt.setContentsMargins(0, 0, 0, 0)
self.searchOpt.setStyleSheet(r"QToolBar {padding: 0;}")
- self.searchLabel = QLabel("Search")
+ self.searchLabel = QLabel(self.tr("Search"))
self.searchLabel.setFont(boxFont)
self.searchLabel.setIndent(self.mainConf.pxInt(6))
- self.toggleCase = QAction("Case Sensitive", self)
- self.toggleCase.setToolTip("Match case")
+ self.toggleCase = QAction(self.tr("Case Sensitive"), self)
+ self.toggleCase.setToolTip(self.tr("Match case"))
self.toggleCase.setIcon(self.theTheme.getIcon("search_case"))
self.toggleCase.setCheckable(True)
self.toggleCase.setChecked(self.isCaseSense)
self.toggleCase.toggled.connect(self._doToggleCase)
self.searchOpt.addAction(self.toggleCase)
- self.toggleWord = QAction("Whole Words Only", self)
- self.toggleWord.setToolTip("Match whole words")
+ self.toggleWord = QAction(self.tr("Whole Words Only"), self)
+ self.toggleWord.setToolTip(self.tr("Match whole words"))
self.toggleWord.setIcon(self.theTheme.getIcon("search_word"))
self.toggleWord.setCheckable(True)
self.toggleWord.setChecked(self.isWholeWord)
self.toggleWord.toggled.connect(self._doToggleWord)
self.searchOpt.addAction(self.toggleWord)
- self.toggleRegEx = QAction("RegEx Mode", self)
- self.toggleRegEx.setToolTip("Use regular expressions (requires Qt 5.3)")
+ self.toggleRegEx = QAction(self.tr("RegEx Mode"), self)
+ self.toggleRegEx.setToolTip(self.tr("Use regular expressions (requires Qt {0})").format(
+ "5.3"))
self.toggleRegEx.setIcon(self.theTheme.getIcon("search_regex"))
self.toggleRegEx.setCheckable(True)
self.toggleRegEx.setChecked(self.isRegEx)
self.toggleRegEx.toggled.connect(self._doToggleRegEx)
self.searchOpt.addAction(self.toggleRegEx)
- self.toggleLoop = QAction("Loop Search", self)
- self.toggleLoop.setToolTip("Loop the search when reaching the end")
+ self.toggleLoop = QAction(self.tr("Loop Search"), self)
+ self.toggleLoop.setToolTip(self.tr("Loop the search when reaching the end"))
self.toggleLoop.setIcon(self.theTheme.getIcon("search_loop"))
self.toggleLoop.setCheckable(True)
self.toggleLoop.setChecked(self.doLoop)
self.toggleLoop.toggled.connect(self._doToggleLoop)
self.searchOpt.addAction(self.toggleLoop)
- self.toggleProject = QAction("Search Next File", self)
- self.toggleProject.setToolTip("Continue searching in the next file")
+ self.toggleProject = QAction(self.tr("Search Next File"), self)
+ self.toggleProject.setToolTip(self.tr("Continue searching in the next file"))
self.toggleProject.setIcon(self.theTheme.getIcon("search_project"))
self.toggleProject.setCheckable(True)
self.toggleProject.setChecked(self.doNextFile)
@@ -1877,8 +1893,8 @@ class GuiDocEditSearch(QFrame):
self.searchOpt.addSeparator()
- self.toggleMatchCap = QAction("Preserve Case", self)
- self.toggleMatchCap.setToolTip("Preserve case on replace")
+ self.toggleMatchCap = QAction(self.tr("Preserve Case"), self)
+ self.toggleMatchCap.setToolTip(self.tr("Preserve case on replace"))
self.toggleMatchCap.setIcon(self.theTheme.getIcon("search_preserve"))
self.toggleMatchCap.setCheckable(True)
self.toggleMatchCap.setChecked(self.doMatchCap)
@@ -1887,8 +1903,8 @@ class GuiDocEditSearch(QFrame):
self.searchOpt.addSeparator()
- self.cancelSearch = QAction("Close Search", self)
- self.cancelSearch.setToolTip("Close the search box [Esc]")
+ self.cancelSearch = QAction(self.tr("Close Search"), self)
+ self.cancelSearch.setToolTip(self.tr("Close the search box [{0}]").format("Esc"))
self.cancelSearch.setIcon(self.theTheme.getIcon("search_cancel"))
self.cancelSearch.triggered.connect(self._doClose)
self.searchOpt.addAction(self.cancelSearch)
@@ -1900,18 +1916,18 @@ class GuiDocEditSearch(QFrame):
self.showReplace = QToolButton(self)
self.showReplace.setArrowType(Qt.RightArrow)
self.showReplace.setCheckable(True)
- self.showReplace.setToolTip("Show/hide the replace text box")
+ self.showReplace.setToolTip(self.tr("Show/hide the replace text box"))
self.showReplace.setStyleSheet(r"QToolButton {border: none; background: transparent;}")
self.showReplace.toggled.connect(self._doToggleReplace)
self.searchButton = QPushButton(self.theTheme.getIcon("search"), "")
self.searchButton.setFixedSize(QSize(bPx, bPx))
- self.searchButton.setToolTip("Find in current document")
+ self.searchButton.setToolTip(self.tr("Find in current document"))
self.searchButton.clicked.connect(self._doSearch)
self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"), "")
self.replaceButton.setFixedSize(QSize(bPx, bPx))
- self.replaceButton.setToolTip("Find and replace in current document")
+ self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
self.replaceButton.clicked.connect(self._doReplace)
self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, Qt.AlignLeft)
@@ -2204,7 +2220,7 @@ class GuiDocEditHeader(QWidget):
self.editButton.setStyleSheet(buttonStyle)
self.editButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.editButton.setVisible(False)
- self.editButton.setToolTip("Edit document meta")
+ self.editButton.setToolTip(self.tr("Edit document meta"))
self.editButton.clicked.connect(self._editDocument)
self.searchButton = QToolButton(self)
@@ -2215,7 +2231,7 @@ class GuiDocEditHeader(QWidget):
self.searchButton.setStyleSheet(buttonStyle)
self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.searchButton.setVisible(False)
- self.searchButton.setToolTip("Search document")
+ self.searchButton.setToolTip(self.tr("Search document"))
self.searchButton.clicked.connect(self._searchDocument)
self.minmaxButton = QToolButton(self)
@@ -2226,7 +2242,7 @@ class GuiDocEditHeader(QWidget):
self.minmaxButton.setStyleSheet(buttonStyle)
self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.minmaxButton.setVisible(False)
- self.minmaxButton.setToolTip("Toggle Focus Mode")
+ self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
self.minmaxButton.clicked.connect(self._minmaxDocument)
self.closeButton = QToolButton(self)
@@ -2237,7 +2253,7 @@ class GuiDocEditHeader(QWidget):
self.closeButton.setStyleSheet(buttonStyle)
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.closeButton.setVisible(False)
- self.closeButton.setToolTip("Close the document")
+ self.closeButton.setToolTip(self.tr("Close the document"))
self.closeButton.clicked.connect(self._closeDocument)
# Assemble Layout
@@ -2413,7 +2429,7 @@ class GuiDocEditFooter(QWidget):
self.statusIcon.setFixedHeight(self.sPx)
self.statusIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
- self.statusText = QLabel("Status")
+ self.statusText = QLabel(self.tr("Status"))
self.statusText.setIndent(0)
self.statusText.setMargin(0)
self.statusText.setContentsMargins(0, 0, 0, 0)
@@ -2429,7 +2445,7 @@ class GuiDocEditFooter(QWidget):
self.linesIcon.setFixedHeight(self.sPx)
self.linesIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
- self.linesText = QLabel("Line: 0")
+ self.linesText = QLabel(self.tr("{0}: {1}").format(self.tr("Line"), "0"))
self.linesText.setIndent(0)
self.linesText.setMargin(0)
self.linesText.setContentsMargins(0, 0, 0, 0)
@@ -2445,7 +2461,7 @@ class GuiDocEditFooter(QWidget):
self.wordsIcon.setFixedHeight(self.sPx)
self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
- self.wordsText = QLabel("Words: 0")
+ self.wordsText = QLabel(self.tr("{0}: {1}").format(self.tr("Words"), "0"))
self.wordsText.setIndent(0)
self.wordsText.setMargin(0)
self.wordsText.setContentsMargins(0, 0, 0, 0)
@@ -2532,8 +2548,10 @@ class GuiDocEditFooter(QWidget):
theIcon = self.theParent.importIcons[iStatus]
sIcon = theIcon.pixmap(self.sPx, self.sPx)
- sClass = nwLabels.CLASS_NAME[self.theItem.itemClass]
- sLayout = nwLabels.LAYOUT_NAME[self.theItem.itemLayout]
+ sClass = QCoreApplication.translate(
+ "Constant", nwLabels.CLASS_NAME[self.theItem.itemClass])
+ sLayout = QCoreApplication.translate(
+ "Constant", nwLabels.LAYOUT_NAME[self.theItem.itemLayout])
sText = f"{self.theItem.itemStatus} / {sClass} / {sLayout}"
self.statusIcon.setPixmap(sIcon)
@@ -2552,7 +2570,9 @@ class GuiDocEditFooter(QWidget):
iLine = theCursor.blockNumber() + 1
iDist = 100*iLine/self.docEditor.qDocument.blockCount()
- self.linesText.setText(f"Line: {iLine:n} ({iDist:.0f}\u202f%)")
+ self.linesText.setText(
+ self.tr("{0}: {1} ({2}\u202f%%)".format(
+ self.tr("Line"), f"{iLine:n}", f"{iDist:.0f}")))
return
@@ -2566,10 +2586,13 @@ class GuiDocEditFooter(QWidget):
wCount = self.theItem.wordCount
wDiff = wCount - self.theItem.initCount
- self.wordsText.setText(f"Words: {wCount:n} ({wDiff:+n})")
+ self.wordsText.setText(
+ self.tr("{0}: {1} ({2})".format(
+ self.tr("Words"), f"{wCount:n}", f"{wDiff:+n}")))
byteSize = self.docEditor.qDocument.characterCount()
- self.wordsText.setToolTip(f"Document size is {byteSize:n} bytes")
+ self.wordsText.setToolTip(
+ (self.tr("Document size is {0} bytes").format(f"{byteSize:n}")))
return
diff --git a/nw/gui/docmerge.py b/nw/gui/docmerge.py
index d3004ec1..c62bd5d1 100644
--- a/nw/gui/docmerge.py
+++ b/nw/gui/docmerge.py
@@ -53,11 +53,11 @@ class GuiDocMerge(QDialog):
self.sourceItem = None
self.outerBox = QVBoxLayout()
- self.setWindowTitle("Merge Documents")
+ self.setWindowTitle(self.tr("Merge Documents"))
- self.headLabel = QLabel("Documents to Merge")
+ self.headLabel = QLabel("%s" % self.tr("Documents to Merge"))
self.helpLabel = QHelpLabel(
- "Drag and drop items to change the order.", self.theParent.theTheme.helpText
+ self.tr("Drag and drop items to change the order."), self.theParent.theTheme.helpText
)
self.listBox = QListWidget()
@@ -66,6 +66,8 @@ 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.Cancel).setText(self.tr("Cancel"))
self.buttonBox.accepted.connect(self._doMerge)
self.buttonBox.rejected.connect(self._doClose)
@@ -103,7 +105,7 @@ class GuiDocMerge(QDialog):
if len(finalOrder) == 0:
self.theParent.makeAlert((
- "No source documents found. Nothing to do."
+ self.tr("No source documents found. Nothing to do.")
), nwAlert.ERROR)
return
@@ -115,14 +117,14 @@ class GuiDocMerge(QDialog):
if self.sourceItem is None:
self.theParent.makeAlert((
- "No source document selected. Nothing to do."
+ self.tr("No source document selected. Nothing to do.")
), nwAlert.ERROR)
return
srcItem = self.theProject.projTree[self.sourceItem]
if srcItem is None:
self.theParent.makeAlert((
- "Could not parse source document."
+ self.tr("Could not parse source document.")
), nwAlert.ERROR)
return
@@ -165,7 +167,7 @@ class GuiDocMerge(QDialog):
return
if nwItem.itemType is not nwItemType.FOLDER:
self.theParent.makeAlert((
- "Element selected in the project tree must be a folder."
+ self.tr("Element selected in the project tree must be a folder.")
), nwAlert.ERROR)
return
diff --git a/nw/gui/docsplit.py b/nw/gui/docsplit.py
index a06aaa67..ae611b4b 100644
--- a/nw/gui/docsplit.py
+++ b/nw/gui/docsplit.py
@@ -56,11 +56,12 @@ class GuiDocSplit(QDialog):
self.sourceItem = None
self.outerBox = QVBoxLayout()
- self.setWindowTitle("Split Document")
+ self.setWindowTitle(self.tr("Split Document"))
- self.headLabel = QLabel("Document Headers")
+ self.headLabel = QLabel("%s" % self.tr("Document Headers"))
self.helpLabel = QHelpLabel(
- "Select the maximum level to split into files.", self.theParent.theTheme.helpText
+ self.tr("Select the maximum level to split into files."),
+ self.theParent.theTheme.helpText
)
self.listBox = QListWidget()
@@ -69,10 +70,10 @@ class GuiDocSplit(QDialog):
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
self.splitLevel = QComboBox(self)
- self.splitLevel.addItem("Split on Header Level 1 (Title)", 1)
- self.splitLevel.addItem("Split up to Header Level 2 (Chapter)", 2)
- self.splitLevel.addItem("Split up to Header Level 3 (Scene)", 3)
- self.splitLevel.addItem("Split up to Header Level 4 (Section)", 4)
+ self.splitLevel.addItem(self.tr("Split on Header Level 1 (Title)"), 1)
+ self.splitLevel.addItem(self.tr("Split up to Header Level 2 (Chapter)"), 2)
+ self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3)
+ self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
spIndex = self.splitLevel.findData(
self.optState.getInt("GuiDocSplit", "spLevel", 3)
)
@@ -81,6 +82,8 @@ 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.Cancel).setText(self.tr("Cancel"))
self.buttonBox.accepted.connect(self._doSplit)
self.buttonBox.rejected.connect(self._doClose)
@@ -116,14 +119,14 @@ class GuiDocSplit(QDialog):
if self.sourceItem is None:
self.theParent.makeAlert((
- "No source document selected. Nothing to do."
+ self.tr("No source document selected. Nothing to do.")
), nwAlert.ERROR)
return
srcItem = self.theProject.projTree[self.sourceItem]
if srcItem is None:
self.theParent.makeAlert((
- "Could not parse source document."
+ self.tr("Could not parse source document.")
), nwAlert.ERROR)
return
@@ -148,7 +151,7 @@ class GuiDocSplit(QDialog):
nFiles = len(finalOrder)
if nFiles == 0:
self.theParent.makeAlert((
- "No headers found. Nothing to do."
+ self.tr("No headers found. Nothing to do.")
), nwAlert.ERROR)
return
@@ -156,17 +159,17 @@ class GuiDocSplit(QDialog):
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
if len(parTree) >= nwConst.MAX_DEPTH - 1:
self.theParent.makeAlert((
- "Cannot add new folder for the document split. "
- "Maximum folder depth has been reached. "
- "Please move the file to another level in the project tree."
+ self.tr("Cannot add new folder for the document split. "
+ "Maximum folder depth has been reached. "
+ "Please move the file to another level in the project tree.")
), nwAlert.ERROR)
return
- msgYes = self.theParent.askQuestion("Split Document", (
- "The document will be split into %d file(s) in a new folder. "
- "The original document will remain intact.
"
- "Continue with the splitting process?"
- ) % nFiles)
+ msgYes = self.theParent.askQuestion(self.tr("Split Document"), "%s
%s" % (
+ self.tr("The document will be split into {0} file(s) in a new folder. "
+ "The original document will remain intact.", n=nFiles).format(nFiles),
+ self.tr("Continue with the splitting process?")
+ ))
if not msgYes:
return
@@ -243,7 +246,7 @@ class GuiDocSplit(QDialog):
return
if nwItem.itemType is not nwItemType.FILE:
self.theParent.makeAlert((
- "Element selected in the project tree must be a file."
+ self.tr("Element selected in the project tree must be a file.")
), nwAlert.ERROR)
return
diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py
index ddc0d876..0b34ebeb 100644
--- a/nw/gui/docviewer.py
+++ b/nw/gui/docviewer.py
@@ -183,7 +183,7 @@ class GuiDocViewer(QTextBrowser):
except Exception:
logger.error("Failed to generate preview for document with handle '%s'" % tHandle)
nw.logException()
- self.setText("An error occurred while generating the preview.")
+ self.setText(self.tr("An error occurred while generating the preview."))
return False
# Refresh the tab stops
@@ -243,11 +243,11 @@ class GuiDocViewer(QTextBrowser):
logger.debug("Loading document from tag '%s'" % theTag)
tHandle, _, sTitle = self.theParent.theIndex.getTagSource(theTag)
if tHandle is None:
- self.theParent.makeAlert((
- "Could not find the reference for tag '%s'. It either doesn't "
- "exist, or the index is out of date. The index can be updated "
- "from the Tools menu, or by pressing F9."
- ) % theTag, nwAlert.ERROR)
+ self.theParent.makeAlert(
+ self.tr("Could not find the reference for tag '{0}'. It either doesn't "
+ "exist, or the index is out of date. The index can be updated "
+ "from the Tools menu, or by pressing {1}.").
+ format(theTag, "F9"), nwAlert.ERROR)
return False
else:
# Let the parent handle the opening as it also ensures that
@@ -415,7 +415,7 @@ class GuiDocViewer(QTextBrowser):
# ===================
if userSelection:
- mnuCopy = QAction("Copy", mnuContext)
+ mnuCopy = QAction(self.tr("Copy"), mnuContext)
mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
mnuContext.addAction(mnuCopy)
@@ -424,17 +424,17 @@ class GuiDocViewer(QTextBrowser):
# Selections
# ==========
- mnuSelAll = QAction("Select All", mnuContext)
+ mnuSelAll = QAction(self.tr("Select All"), mnuContext)
mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
mnuContext.addAction(mnuSelAll)
- mnuSelWord = QAction("Select Word", mnuContext)
+ mnuSelWord = QAction(self.tr("Select Word"), mnuContext)
mnuSelWord.triggered.connect(
lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos)
)
mnuContext.addAction(mnuSelWord)
- mnuSelPara = QAction("Select Paragraph", mnuContext)
+ mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext)
mnuSelPara.triggered.connect(
lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos)
)
@@ -740,7 +740,7 @@ class GuiDocViewHeader(QWidget):
self.backButton.setStyleSheet(buttonStyle)
self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.backButton.setVisible(False)
- self.backButton.setToolTip("Go backward")
+ self.backButton.setToolTip(self.tr("Go backward"))
self.backButton.clicked.connect(self.docViewer.navBackward)
self.forwardButton = QToolButton(self)
@@ -751,7 +751,7 @@ class GuiDocViewHeader(QWidget):
self.forwardButton.setStyleSheet(buttonStyle)
self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.forwardButton.setVisible(False)
- self.forwardButton.setToolTip("Go forward")
+ self.forwardButton.setToolTip(self.tr("Go forward"))
self.forwardButton.clicked.connect(self.docViewer.navForward)
self.refreshButton = QToolButton(self)
@@ -762,7 +762,7 @@ class GuiDocViewHeader(QWidget):
self.refreshButton.setStyleSheet(buttonStyle)
self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.refreshButton.setVisible(False)
- self.refreshButton.setToolTip("Reload the document")
+ self.refreshButton.setToolTip(self.tr("Reload the document"))
self.refreshButton.clicked.connect(self._refreshDocument)
self.closeButton = QToolButton(self)
@@ -773,7 +773,7 @@ class GuiDocViewHeader(QWidget):
self.closeButton.setStyleSheet(buttonStyle)
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.closeButton.setVisible(False)
- self.closeButton.setToolTip("Close the document")
+ self.closeButton.setToolTip(self.tr("Close the document"))
self.closeButton.clicked.connect(self._closeDocument)
# Assemble Layout
@@ -944,7 +944,7 @@ class GuiDocViewFooter(QWidget):
self.showHide.setIconSize(QSize(fPx, fPx))
self.showHide.setFixedSize(QSize(fPx, fPx))
self.showHide.clicked.connect(self._doShowHide)
- self.showHide.setToolTip("Show/hide the references panel")
+ self.showHide.setToolTip(self.tr("Show/hide the references panel"))
# Sticky Button
self.stickyRefs = QToolButton(self)
@@ -956,7 +956,8 @@ class GuiDocViewFooter(QWidget):
self.stickyRefs.setFixedSize(QSize(fPx, fPx))
self.stickyRefs.toggled.connect(self._doToggleSticky)
self.stickyRefs.setToolTip(
- "Activate to freeze the content of the references panel when changing document"
+ self.tr("Activate to freeze the content of the references panel when "
+ "changing document")
)
# Show Comments
@@ -969,7 +970,7 @@ class GuiDocViewFooter(QWidget):
self.showComments.setIconSize(QSize(fPx, fPx))
self.showComments.setFixedSize(QSize(fPx, fPx))
self.showComments.toggled.connect(self._doToggleComments)
- self.showComments.setToolTip("Show comments")
+ self.showComments.setToolTip(self.tr("Show comments"))
# Show Synopsis
self.showSynopsis = QToolButton(self)
@@ -981,10 +982,10 @@ class GuiDocViewFooter(QWidget):
self.showSynopsis.setIconSize(QSize(fPx, fPx))
self.showSynopsis.setFixedSize(QSize(fPx, fPx))
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
- self.showSynopsis.setToolTip("Show synopsis comments")
+ self.showSynopsis.setToolTip(self.tr("Show synopsis comments"))
# Labels
- self.lblRefs = QLabel("References")
+ self.lblRefs = QLabel(self.tr("References"))
self.lblRefs.setBuddy(self.showHide)
self.lblRefs.setIndent(0)
self.lblRefs.setMargin(0)
@@ -993,7 +994,7 @@ class GuiDocViewFooter(QWidget):
self.lblRefs.setFixedHeight(fPx)
self.lblRefs.setAlignment(Qt.AlignLeft | Qt.AlignTop)
- self.lblSticky = QLabel("Sticky")
+ self.lblSticky = QLabel(self.tr("Sticky"))
self.lblSticky.setBuddy(self.stickyRefs)
self.lblSticky.setIndent(0)
self.lblSticky.setMargin(0)
@@ -1002,7 +1003,7 @@ class GuiDocViewFooter(QWidget):
self.lblSticky.setFixedHeight(fPx)
self.lblSticky.setAlignment(Qt.AlignLeft | Qt.AlignTop)
- self.lblComments = QLabel("Comments")
+ self.lblComments = QLabel(self.tr("Comments"))
self.lblComments.setBuddy(self.showComments)
self.lblComments.setIndent(0)
self.lblComments.setMargin(0)
@@ -1011,7 +1012,7 @@ class GuiDocViewFooter(QWidget):
self.lblComments.setFixedHeight(fPx)
self.lblComments.setAlignment(Qt.AlignLeft | Qt.AlignTop)
- self.lblSynopsis = QLabel("Synopsis")
+ self.lblSynopsis = QLabel(self.tr("Synopsis"))
self.lblSynopsis.setBuddy(self.showSynopsis)
self.lblSynopsis.setIndent(0)
self.lblSynopsis.setMargin(0)
diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py
index 52584d40..ec77b2c9 100644
--- a/nw/gui/itemdetails.py
+++ b/nw/gui/itemdetails.py
@@ -27,7 +27,7 @@ along with this program. If not, see .
import nw
import logging
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import QCoreApplication, Qt
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
@@ -67,7 +67,7 @@ class GuiItemDetails(QWidget):
self.fntValue.setPointSizeF(0.9*fPt)
# Label
- self.labelName = QLabel("Label")
+ self.labelName = QLabel(self.tr("Label"))
self.labelName.setFont(self.fntLabel)
self.labelName.setAlignment(Qt.AlignLeft | Qt.AlignBaseline)
@@ -80,7 +80,7 @@ class GuiItemDetails(QWidget):
self.labelData.setWordWrap(True)
# Status
- self.statusName = QLabel("Status")
+ self.statusName = QLabel(self.tr("Status"))
self.statusName.setFont(self.fntLabel)
self.statusName.setAlignment(Qt.AlignLeft)
@@ -92,7 +92,7 @@ class GuiItemDetails(QWidget):
self.statusData.setAlignment(Qt.AlignLeft)
# Class
- self.className = QLabel("Class")
+ self.className = QLabel(self.tr("Class"))
self.className.setFont(self.fntLabel)
self.className.setAlignment(Qt.AlignLeft)
@@ -105,7 +105,7 @@ class GuiItemDetails(QWidget):
self.classData.setAlignment(Qt.AlignLeft)
# Layout
- self.layoutName = QLabel("Layout")
+ self.layoutName = QLabel(self.tr("Layout"))
self.layoutName.setFont(self.fntLabel)
self.layoutName.setAlignment(Qt.AlignLeft)
@@ -118,7 +118,7 @@ class GuiItemDetails(QWidget):
self.layoutData.setAlignment(Qt.AlignLeft)
# Character Count
- self.cCountName = QLabel(" Characters")
+ self.cCountName = QLabel(self.tr(" Characters"))
self.cCountName.setFont(self.fntLabel)
self.cCountName.setAlignment(Qt.AlignRight)
@@ -127,7 +127,7 @@ class GuiItemDetails(QWidget):
self.cCountData.setAlignment(Qt.AlignRight)
# Word Count
- self.wCountName = QLabel(" Words")
+ self.wCountName = QLabel(self.tr(" Words"))
self.wCountName.setFont(self.fntLabel)
self.wCountName.setAlignment(Qt.AlignRight)
@@ -136,7 +136,7 @@ class GuiItemDetails(QWidget):
self.wCountData.setAlignment(Qt.AlignRight)
# Paragraph Count
- self.pCountName = QLabel(" Paragraphs")
+ self.pCountName = QLabel(self.tr(" Paragraphs"))
self.pCountName.setFont(self.fntLabel)
self.pCountName.setAlignment(Qt.AlignRight)
@@ -259,17 +259,19 @@ class GuiItemDetails(QWidget):
iPx = int(round(0.8*self.theTheme.baseIconSize))
self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx))
- self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass])
+ self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) # NO-I18N
if nwItem.itemLayout == nwItemLayout.NO_LAYOUT:
self.layoutFlag.setText("-")
else:
- self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout])
+ self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) # NO-I18N
self.labelData.setText(theLabel)
self.statusData.setText(nwItem.itemStatus)
- self.classData.setText(nwLabels.CLASS_NAME[nwItem.itemClass])
- self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout])
+ self.classData.setText(QCoreApplication.translate(
+ "Constant", nwLabels.CLASS_NAME[nwItem.itemClass]))
+ self.layoutData.setText(QCoreApplication.translate(
+ "Constant", nwLabels.LAYOUT_NAME[nwItem.itemLayout]))
if nwItem.itemType == nwItemType.FILE:
self.cCountData.setText(f"{nwItem.charCount:n}")
diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py
index 63f67021..37154d86 100644
--- a/nw/gui/itemeditor.py
+++ b/nw/gui/itemeditor.py
@@ -27,7 +27,7 @@ along with this program. If not, see .
import nw
import logging
-from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtCore import QCoreApplication, pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
QDialogButtonBox
@@ -58,7 +58,7 @@ class GuiItemEditor(QDialog):
if self.theItem is None:
self._doClose()
- self.setWindowTitle("Item Settings")
+ self.setWindowTitle(self.tr("Item Settings"))
mVd = self.mainConf.pxInt(220)
mSp = self.mainConf.pxInt(16)
@@ -103,10 +103,11 @@ class GuiItemEditor(QDialog):
for itemLayout in nwItemLayout:
if itemLayout in validLayouts:
- self.editLayout.addItem(nwLabels.LAYOUT_NAME[itemLayout], itemLayout)
+ self.editLayout.addItem(QCoreApplication.translate(
+ "Constant", nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
# Export Switch
- self.textExport = QLabel("Include when building project")
+ self.textExport = QLabel(self.tr("Include when building project"))
self.editExport = QSwitch()
if self.theItem.itemType == nwItemType.FILE:
self.editExport.setEnabled(True)
@@ -117,6 +118,8 @@ class GuiItemEditor(QDialog):
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ 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)
@@ -139,11 +142,11 @@ class GuiItemEditor(QDialog):
self.mainForm = QGridLayout()
self.mainForm.setVerticalSpacing(vSp)
self.mainForm.setHorizontalSpacing(mSp)
- self.mainForm.addWidget(QLabel("Label"), 0, 0, 1, 1)
+ self.mainForm.addWidget(QLabel(self.tr("Label")), 0, 0, 1, 1)
self.mainForm.addWidget(self.editName, 0, 1, 1, 2)
- self.mainForm.addWidget(QLabel("Status"), 1, 0, 1, 1)
+ self.mainForm.addWidget(QLabel(self.tr("Status")), 1, 0, 1, 1)
self.mainForm.addWidget(self.editStatus, 1, 1, 1, 2)
- self.mainForm.addWidget(QLabel("Layout"), 2, 0, 1, 1)
+ self.mainForm.addWidget(QLabel(self.tr("Layout")), 2, 0, 1, 1)
self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2)
self.mainForm.addWidget(self.textExport, 3, 0, 1, 2)
self.mainForm.addWidget(self.editExport, 3, 2, 1, 1)
diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py
index 6d6267a9..dd2da1bd 100644
--- a/nw/gui/mainmenu.py
+++ b/nw/gui/mainmenu.py
@@ -27,7 +27,7 @@ along with this program. If not, see .
import nw
import logging
-from PyQt5.QtCore import QUrl, QProcess
+from PyQt5.QtCore import QCoreApplication, QUrl, QProcess
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction
@@ -180,31 +180,31 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Project menu.
"""
# Project
- self.projMenu = self.addMenu("&Project")
+ self.projMenu = self.addMenu(self.tr("&Project"))
# Project > New Project
- self.aNewProject = QAction("New Project", self)
- self.aNewProject.setStatusTip("Create new project")
+ self.aNewProject = QAction(self.tr("New Project"), self)
+ self.aNewProject.setStatusTip(self.tr("Create new project"))
self.aNewProject.triggered.connect(lambda: self.theParent.newProject(None))
self.projMenu.addAction(self.aNewProject)
# Project > Open Project
- self.aOpenProject = QAction("Open Project", self)
- self.aOpenProject.setStatusTip("Open project")
+ self.aOpenProject = QAction(self.tr("Open Project"), self)
+ self.aOpenProject.setStatusTip(self.tr("Open project"))
self.aOpenProject.setShortcut("Ctrl+Shift+O")
self.aOpenProject.triggered.connect(lambda: self.theParent.showProjectLoadDialog())
self.projMenu.addAction(self.aOpenProject)
# Project > Save Project
- self.aSaveProject = QAction("Save Project", self)
- self.aSaveProject.setStatusTip("Save project")
+ self.aSaveProject = QAction(self.tr("Save Project"), self)
+ self.aSaveProject.setStatusTip(self.tr("Save project"))
self.aSaveProject.setShortcut("Ctrl+Shift+S")
self.aSaveProject.triggered.connect(lambda: self.theParent.saveProject())
self.projMenu.addAction(self.aSaveProject)
# Project > Close Project
- self.aCloseProject = QAction("Close Project", self)
- self.aCloseProject.setStatusTip("Close project")
+ self.aCloseProject = QAction(self.tr("Close Project"), self)
+ self.aCloseProject.setStatusTip(self.tr("Close project"))
self.aCloseProject.setShortcut("Ctrl+Shift+W")
self.aCloseProject.triggered.connect(lambda: self.theParent.closeProject(False))
self.projMenu.addAction(self.aCloseProject)
@@ -213,15 +213,15 @@ class GuiMainMenu(QMenuBar):
self.projMenu.addSeparator()
# Project > Project Settings
- self.aProjectSettings = QAction("Project Settings", self)
- self.aProjectSettings.setStatusTip("Project settings")
+ self.aProjectSettings = QAction(self.tr("Project Settings"), self)
+ self.aProjectSettings.setStatusTip(self.tr("Project settings"))
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
self.aProjectSettings.triggered.connect(lambda: self.theParent.showProjectSettingsDialog())
self.projMenu.addAction(self.aProjectSettings)
# Project > Project Details
- self.aProjectDetails = QAction("Project Details", self)
- self.aProjectDetails.setStatusTip("Project details")
+ self.aProjectDetails = QAction(self.tr("Project Details"), self)
+ self.aProjectDetails.setStatusTip(self.tr("Project details"))
self.aProjectDetails.setShortcut("Shift+F6")
self.aProjectDetails.triggered.connect(lambda: self.theParent.showProjectDetailsDialog())
self.projMenu.addAction(self.aProjectDetails)
@@ -230,17 +230,17 @@ class GuiMainMenu(QMenuBar):
self.projMenu.addSeparator()
# Project > New Root
- self.rootMenu = self.projMenu.addMenu("Create Root Folder")
+ self.rootMenu = self.projMenu.addMenu(self.tr("Create Root Folder"))
self.rootItems = {}
- self.rootItems[nwItemClass.NOVEL] = QAction("Novel Root", self.rootMenu)
- self.rootItems[nwItemClass.PLOT] = QAction("Plot Root", self.rootMenu)
- self.rootItems[nwItemClass.CHARACTER] = QAction("Character Root", self.rootMenu)
- self.rootItems[nwItemClass.WORLD] = QAction("Location Root", self.rootMenu)
- self.rootItems[nwItemClass.TIMELINE] = QAction("Timeline Root", self.rootMenu)
- self.rootItems[nwItemClass.OBJECT] = QAction("Object Root", self.rootMenu)
- self.rootItems[nwItemClass.ENTITY] = QAction("Entity Root", self.rootMenu)
- self.rootItems[nwItemClass.CUSTOM] = QAction("Custom Root", self.rootMenu)
- self.rootItems[nwItemClass.ARCHIVE] = QAction("Outtakes Root", self.rootMenu)
+ self.rootItems[nwItemClass.NOVEL] = QAction(self.tr("Novel Root"), self.rootMenu)
+ self.rootItems[nwItemClass.PLOT] = QAction(self.tr("Plot Root"), self.rootMenu)
+ self.rootItems[nwItemClass.CHARACTER] = QAction(self.tr("Character Root"), self.rootMenu)
+ self.rootItems[nwItemClass.WORLD] = QAction(self.tr("Location Root"), self.rootMenu)
+ self.rootItems[nwItemClass.TIMELINE] = QAction(self.tr("Timeline Root"), self.rootMenu)
+ self.rootItems[nwItemClass.OBJECT] = QAction(self.tr("Object Root"), self.rootMenu)
+ self.rootItems[nwItemClass.ENTITY] = QAction(self.tr("Entity Root"), self.rootMenu)
+ self.rootItems[nwItemClass.CUSTOM] = QAction(self.tr("Custom Root"), self.rootMenu)
+ self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Outtakes Root"), self.rootMenu)
nCount = 0
for itemClass in self.rootItems.keys():
nCount += 1 # This forces the lambdas to be unique
@@ -250,8 +250,8 @@ class GuiMainMenu(QMenuBar):
self.rootMenu.addAction(self.rootItems[itemClass])
# Project > New Folder
- self.aCreateFolder = QAction("Create Folder", self)
- self.aCreateFolder.setStatusTip("Create folder")
+ self.aCreateFolder = QAction(self.tr("Create Folder"), self)
+ self.aCreateFolder.setStatusTip(self.tr("Create folder"))
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER, None))
self.projMenu.addAction(self.aCreateFolder)
@@ -260,43 +260,43 @@ class GuiMainMenu(QMenuBar):
self.projMenu.addSeparator()
# Project > Edit
- self.aEditItem = QAction("Edit Item", self)
- self.aEditItem.setStatusTip("Change project item settings")
+ self.aEditItem = QAction(self.tr("Edit Item"), self)
+ self.aEditItem.setStatusTip(self.tr("Change project item settings"))
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
self.aEditItem.triggered.connect(lambda: self.theParent.editItem(None))
self.projMenu.addAction(self.aEditItem)
# Project > Delete
- self.aDeleteItem = QAction("Delete Item", self)
- self.aDeleteItem.setStatusTip("Delete selected project item")
+ self.aDeleteItem = QAction(self.tr("Delete Item"), self)
+ self.aDeleteItem.setStatusTip(self.tr("Delete selected project item"))
self.aDeleteItem.setShortcut("Ctrl+Shift+Del")
self.aDeleteItem.triggered.connect(lambda: self.theParent.treeView.deleteItem(None))
self.projMenu.addAction(self.aDeleteItem)
# Project > Move Up
- self.aMoveUp = QAction("Move Item Up", self)
- self.aMoveUp.setStatusTip("Move project item up")
+ self.aMoveUp = QAction(self.tr("Move Item Up"), self)
+ self.aMoveUp.setStatusTip(self.tr("Move project item up"))
self.aMoveUp.setShortcut("Ctrl+Up")
self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1))
self.projMenu.addAction(self.aMoveUp)
# Project > Move Down
- self.aMoveDown = QAction("Move Item Down", self)
- self.aMoveDown.setStatusTip("Move project item down")
+ self.aMoveDown = QAction(self.tr("Move Item Down"), self)
+ self.aMoveDown.setStatusTip(self.tr("Move project item down"))
self.aMoveDown.setShortcut("Ctrl+Down")
self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1))
self.projMenu.addAction(self.aMoveDown)
# Project > Undo Last Action
- self.aMoveUndo = QAction("Undo Last Move", self)
- self.aMoveUndo.setStatusTip("Undo last item move")
+ self.aMoveUndo = QAction(self.tr("Undo Last Move"), self)
+ self.aMoveUndo.setStatusTip(self.tr("Undo last item move"))
self.aMoveUndo.setShortcut("Ctrl+Shift+Z")
self.aMoveUndo.triggered.connect(lambda: self.theParent.treeView.undoLastMove())
self.projMenu.addAction(self.aMoveUndo)
# Project > Empty Trash
- self.aEmptyTrash = QAction("Empty Trash", self)
- self.aEmptyTrash.setStatusTip("Permanently delete all files in the Trash folder")
+ self.aEmptyTrash = QAction(self.tr("Empty Trash"), self)
+ self.aEmptyTrash.setStatusTip(self.tr("Permanently delete all files in the Trash folder"))
self.aEmptyTrash.triggered.connect(lambda: self.theParent.treeView.emptyTrash())
self.projMenu.addAction(self.aEmptyTrash)
@@ -304,8 +304,8 @@ class GuiMainMenu(QMenuBar):
self.projMenu.addSeparator()
# Project > Exit
- self.aExitNW = QAction("Exit", self)
- self.aExitNW.setStatusTip("Exit novelWriter")
+ self.aExitNW = QAction(self.tr("Exit"), self)
+ self.aExitNW.setStatusTip(self.tr("Exit novelWriter"))
self.aExitNW.setShortcut("Ctrl+Q")
self.aExitNW.setMenuRole(QAction.QuitRole)
self.aExitNW.triggered.connect(lambda: self.theParent.closeMain())
@@ -317,32 +317,32 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Document menu.
"""
# Document
- self.docuMenu = self.addMenu("&Document")
+ self.docuMenu = self.addMenu(self.tr("&Document"))
# Document > New
- self.aNewDoc = QAction("New Document", self)
- self.aNewDoc.setStatusTip("Create new document")
+ self.aNewDoc = QAction(self.tr("New Document"), self)
+ self.aNewDoc.setStatusTip(self.tr("Create new document"))
self.aNewDoc.setShortcut("Ctrl+N")
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE, None))
self.docuMenu.addAction(self.aNewDoc)
# Document > Open
- self.aOpenDoc = QAction("Open Document", self)
- self.aOpenDoc.setStatusTip("Open selected document")
+ self.aOpenDoc = QAction(self.tr("Open Document"), self)
+ self.aOpenDoc.setStatusTip(self.tr("Open selected document"))
self.aOpenDoc.setShortcut("Ctrl+O")
self.aOpenDoc.triggered.connect(lambda: self.theParent.openSelectedItem())
self.docuMenu.addAction(self.aOpenDoc)
# Document > Save
- self.aSaveDoc = QAction("Save Document", self)
- self.aSaveDoc.setStatusTip("Save current document")
+ self.aSaveDoc = QAction(self.tr("Save Document"), self)
+ self.aSaveDoc.setStatusTip(self.tr("Save current document"))
self.aSaveDoc.setShortcut("Ctrl+S")
self.aSaveDoc.triggered.connect(lambda: self.theParent.saveDocument())
self.docuMenu.addAction(self.aSaveDoc)
# Document > Close
- self.aCloseDoc = QAction("Close Document", self)
- self.aCloseDoc.setStatusTip("Close current document")
+ self.aCloseDoc = QAction(self.tr("Close Document"), self)
+ self.aCloseDoc.setStatusTip(self.tr("Close current document"))
self.aCloseDoc.setShortcut("Ctrl+W")
self.aCloseDoc.triggered.connect(lambda: self.theParent.closeDocEditor())
self.docuMenu.addAction(self.aCloseDoc)
@@ -351,15 +351,15 @@ class GuiMainMenu(QMenuBar):
self.docuMenu.addSeparator()
# Document > Preview
- self.aViewDoc = QAction("View Document", self)
- self.aViewDoc.setStatusTip("View document as HTML")
+ self.aViewDoc = QAction(self.tr("View Document"), self)
+ self.aViewDoc.setStatusTip(self.tr("View document as HTML"))
self.aViewDoc.setShortcut("Ctrl+R")
self.aViewDoc.triggered.connect(lambda: self.theParent.viewDocument(None))
self.docuMenu.addAction(self.aViewDoc)
# Document > Close Preview
- self.aCloseView = QAction("Close Document View", self)
- self.aCloseView.setStatusTip("Close document view pane")
+ self.aCloseView = QAction(self.tr("Close Document View"), self)
+ self.aCloseView.setStatusTip(self.tr("Close document view pane"))
self.aCloseView.setShortcut("Ctrl+Shift+R")
self.aCloseView.triggered.connect(lambda: self.theParent.closeDocViewer())
self.docuMenu.addAction(self.aCloseView)
@@ -368,29 +368,30 @@ class GuiMainMenu(QMenuBar):
self.docuMenu.addSeparator()
# Document > Show File Details
- self.aFileDetails = QAction("Show File Details", self)
+ self.aFileDetails = QAction(self.tr("Show File Details"), self)
self.aFileDetails.setStatusTip(
- "Shows a message box with the document location in the project folder"
+ self.tr("Shows a message box with the document location in the project folder")
)
self.aFileDetails.triggered.connect(lambda: self.theParent.docEditor.revealLocation())
self.docuMenu.addAction(self.aFileDetails)
# Document > Import From File
- self.aImportFile = QAction("Import from File", self)
- self.aImportFile.setStatusTip("Import document from a text or markdown file")
+ self.aImportFile = QAction(self.tr("Import from File"), self)
+ self.aImportFile.setStatusTip(self.tr("Import document from a text or markdown file"))
self.aImportFile.setShortcut("Ctrl+Shift+I")
self.aImportFile.triggered.connect(lambda: self.theParent.importDocument())
self.docuMenu.addAction(self.aImportFile)
# Document > Merge Documents
- self.aMergeDocs = QAction("Merge Folder to Document", self)
- self.aMergeDocs.setStatusTip("Merge a folder of documents to a single document")
+ self.aMergeDocs = QAction(self.tr("Merge Folder to Document"), self)
+ self.aMergeDocs.setStatusTip(self.tr("Merge a folder of documents to a single document"))
self.aMergeDocs.triggered.connect(lambda: self.theParent.mergeDocuments())
self.docuMenu.addAction(self.aMergeDocs)
# Document > Split Document
- self.aSplitDoc = QAction("Split Document to Folder", self)
- self.aSplitDoc.setStatusTip("Split a document into a folder of multiple documents")
+ self.aSplitDoc = QAction(self.tr("Split Document to Folder"), self)
+ self.aSplitDoc.setStatusTip(self.tr("Split a document into a folder of "
+ "multiple documents"))
self.aSplitDoc.triggered.connect(lambda: self.theParent.splitDocument())
self.docuMenu.addAction(self.aSplitDoc)
@@ -400,18 +401,18 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Edit menu.
"""
# Edit
- self.editMenu = self.addMenu("&Edit")
+ self.editMenu = self.addMenu(self.tr("&Edit"))
# Edit > Undo
- self.aEditUndo = QAction("Undo", self)
- self.aEditUndo.setStatusTip("Undo last change")
+ self.aEditUndo = QAction(self.tr("Undo"), self)
+ self.aEditUndo.setStatusTip(self.tr("Undo last change"))
self.aEditUndo.setShortcut("Ctrl+Z")
self.aEditUndo.triggered.connect(lambda: self._docAction(nwDocAction.UNDO))
self.editMenu.addAction(self.aEditUndo)
# Edit > Redo
- self.aEditRedo = QAction("Redo", self)
- self.aEditRedo.setStatusTip("Redo last change")
+ self.aEditRedo = QAction(self.tr("Redo"), self)
+ self.aEditRedo.setStatusTip(self.tr("Redo last change"))
self.aEditRedo.setShortcut("Ctrl+Y")
self.aEditRedo.triggered.connect(lambda: self._docAction(nwDocAction.REDO))
self.editMenu.addAction(self.aEditRedo)
@@ -420,22 +421,22 @@ class GuiMainMenu(QMenuBar):
self.editMenu.addSeparator()
# Edit > Cut
- self.aEditCut = QAction("Cut", self)
- self.aEditCut.setStatusTip("Cut selected text")
+ self.aEditCut = QAction(self.tr("Cut"), self)
+ self.aEditCut.setStatusTip(self.tr("Cut selected text"))
self.aEditCut.setShortcut("Ctrl+X")
self.aEditCut.triggered.connect(lambda: self._docAction(nwDocAction.CUT))
self.editMenu.addAction(self.aEditCut)
# Edit > Copy
- self.aEditCopy = QAction("Copy", self)
- self.aEditCopy.setStatusTip("Copy selected text")
+ self.aEditCopy = QAction(self.tr("Copy"), self)
+ self.aEditCopy.setStatusTip(self.tr("Copy selected text"))
self.aEditCopy.setShortcut("Ctrl+C")
self.aEditCopy.triggered.connect(lambda: self._docAction(nwDocAction.COPY))
self.editMenu.addAction(self.aEditCopy)
# Edit > Paste
- self.aEditPaste = QAction("Paste", self)
- self.aEditPaste.setStatusTip("Paste text from clipboard")
+ self.aEditPaste = QAction(self.tr("Paste"), self)
+ self.aEditPaste.setStatusTip(self.tr("Paste text from clipboard"))
self.aEditPaste.setShortcut("Ctrl+V")
self.aEditPaste.triggered.connect(lambda: self._docAction(nwDocAction.PASTE))
self.editMenu.addAction(self.aEditPaste)
@@ -444,15 +445,15 @@ class GuiMainMenu(QMenuBar):
self.editMenu.addSeparator()
# Edit > Select All
- self.aSelectAll = QAction("Select All", self)
- self.aSelectAll.setStatusTip("Select all text in document")
+ self.aSelectAll = QAction(self.tr("Select All"), self)
+ self.aSelectAll.setStatusTip(self.tr("Select all text in document"))
self.aSelectAll.setShortcut("Ctrl+A")
self.aSelectAll.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL))
self.editMenu.addAction(self.aSelectAll)
# Edit > Select Paragraph
- self.aSelectPar = QAction("Select Paragraph", self)
- self.aSelectPar.setStatusTip("Select all text in paragraph")
+ self.aSelectPar = QAction(self.tr("Select Paragraph"), self)
+ self.aSelectPar.setStatusTip(self.tr("Select all text in paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A")
self.aSelectPar.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA))
self.editMenu.addAction(self.aSelectPar)
@@ -463,32 +464,32 @@ class GuiMainMenu(QMenuBar):
"""Assemble the View menu.
"""
# View
- self.viewMenu = self.addMenu("&View")
+ self.viewMenu = self.addMenu(self.tr("&View"))
# View > TreeView
- self.aFocusTree = QAction("Focus Project Tree", self)
- self.aFocusTree.setStatusTip("Move focus to project tree")
+ self.aFocusTree = QAction(self.tr("Focus Project Tree"), self)
+ self.aFocusTree.setStatusTip(self.tr("Move focus to project tree"))
self.aFocusTree.setShortcut("Alt+1")
self.aFocusTree.triggered.connect(lambda: self.theParent.setFocus(1))
self.viewMenu.addAction(self.aFocusTree)
# View > Document Pane 1
- self.aFocusEditor = QAction("Focus Document Editor", self)
- self.aFocusEditor.setStatusTip("Move focus to left document pane")
+ self.aFocusEditor = QAction(self.tr("Focus Document Editor"), self)
+ self.aFocusEditor.setStatusTip(self.tr("Move focus to left document pane"))
self.aFocusEditor.setShortcut("Alt+2")
self.aFocusEditor.triggered.connect(lambda: self.theParent.setFocus(2))
self.viewMenu.addAction(self.aFocusEditor)
# View > Document Pane 2
- self.aFocusView = QAction("Focus Document Viewer", self)
- self.aFocusView.setStatusTip("Move focus to right document pane")
+ self.aFocusView = QAction(self.tr("Focus Document Viewer"), self)
+ self.aFocusView.setStatusTip(self.tr("Move focus to right document pane"))
self.aFocusView.setShortcut("Alt+3")
self.aFocusView.triggered.connect(lambda: self.theParent.setFocus(3))
self.viewMenu.addAction(self.aFocusView)
# View > Outline
- self.aFocusOutline = QAction("Focus Outline", self)
- self.aFocusOutline.setStatusTip("Move focus to outline")
+ self.aFocusOutline = QAction(self.tr("Focus Outline"), self)
+ self.aFocusOutline.setStatusTip(self.tr("Move focus to outline"))
self.aFocusOutline.setShortcut("Alt+4")
self.aFocusOutline.triggered.connect(lambda: self.theParent.setFocus(4))
self.viewMenu.addAction(self.aFocusOutline)
@@ -497,15 +498,15 @@ class GuiMainMenu(QMenuBar):
self.viewMenu.addSeparator()
# View > Go Backward
- self.aViewPrev = QAction("Go Backward", self)
- self.aViewPrev.setStatusTip("Move backward in the view history of the right pane")
+ self.aViewPrev = QAction(self.tr("Go Backward"), self)
+ self.aViewPrev.setStatusTip(self.tr("Move backward in the view history of the right pane"))
self.aViewPrev.setShortcut("Alt+Left")
self.aViewPrev.triggered.connect(lambda: self.theParent.docViewer.navBackward())
self.viewMenu.addAction(self.aViewPrev)
# View > Go Forward
- self.aViewNext = QAction("Go Forward", self)
- self.aViewNext.setStatusTip("Move forward in the view history of the right pane")
+ self.aViewNext = QAction(self.tr("Go Forward"), self)
+ self.aViewNext.setStatusTip(self.tr("Move forward in the view history of the right pane"))
self.aViewNext.setShortcut("Alt+Right")
self.aViewNext.triggered.connect(lambda: self.theParent.docViewer.navForward())
self.viewMenu.addAction(self.aViewNext)
@@ -514,8 +515,9 @@ class GuiMainMenu(QMenuBar):
self.viewMenu.addSeparator()
# View > Focus Mode
- self.aFocusMode = QAction("Focus Mode", self)
- self.aFocusMode.setStatusTip("Toggles a distraction free mode, only showing text editor")
+ self.aFocusMode = QAction(self.tr("Focus Mode"), self)
+ self.aFocusMode.setStatusTip(self.tr("Toggles a distraction free mode, "
+ "only showing text editor"))
self.aFocusMode.setShortcut("F8")
self.aFocusMode.setCheckable(True)
self.aFocusMode.setChecked(self.theParent.isFocusMode)
@@ -523,8 +525,8 @@ class GuiMainMenu(QMenuBar):
self.viewMenu.addAction(self.aFocusMode)
# View > Toggle Full Screen
- self.aFullScreen = QAction("Full Screen Mode", self)
- self.aFullScreen.setStatusTip("Maximises the main window")
+ self.aFullScreen = QAction(self.tr("Full Screen Mode"), self)
+ self.aFullScreen.setStatusTip(self.tr("Maximises the main window"))
self.aFullScreen.setShortcut("F11")
self.aFullScreen.triggered.connect(lambda: self.theParent.toggleFullScreenMode())
self.viewMenu.addAction(self.aFullScreen)
@@ -535,187 +537,188 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Insert menu.
"""
# Insert
- self.insertMenu = self.addMenu("&Insert")
+ self.insertMenu = self.addMenu(self.tr("&Insert"))
- # Insert > Dashes
- self.mInsDashes = self.insertMenu.addMenu("Dashes")
+ # Insert > Dashes and Dots
+ self.mInsDashes = self.insertMenu.addMenu(self.tr("Dashes"))
# Insert > Short Dash
- self.aInsENDash = QAction("Short Dash", self)
- self.aInsENDash.setStatusTip("Insert short dash (en dash)")
+ self.aInsENDash = QAction(self.tr("Short Dash"), self)
+ self.aInsENDash.setStatusTip(self.tr("Insert short dash (en dash)"))
self.aInsENDash.setShortcut("Ctrl+K, -")
self.aInsENDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_ENDASH))
self.mInsDashes.addAction(self.aInsENDash)
# Insert > Long Dash
- self.aInsEMDash = QAction("Long Dash", self)
- self.aInsEMDash.setStatusTip("Insert long dash (em dash)")
+ self.aInsEMDash = QAction(self.tr("Long Dash"), self)
+ self.aInsEMDash.setStatusTip(self.tr("Insert long dash (em dash)"))
self.aInsEMDash.setShortcut("Ctrl+K, _")
self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_EMDASH))
self.mInsDashes.addAction(self.aInsEMDash)
# Insert > Long Dash
- self.aInsHorBar = QAction("Horizontal Bar", self)
- self.aInsHorBar.setStatusTip("Insert a horizontal bar (quotation dash)")
+ self.aInsHorBar = QAction(self.tr("Horizontal Bar"), self)
+ self.aInsHorBar.setStatusTip(self.tr("Insert a horizontal bar (quotation dash)"))
self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_")
self.aInsHorBar.triggered.connect(lambda: self._docInsert(nwUnicode.U_HBAR))
self.mInsDashes.addAction(self.aInsHorBar)
# Insert > Figure Dash
- self.aInsFigDash = QAction("Figure Dash", self)
- self.aInsFigDash.setStatusTip("Insert figure dash (same width as a number character)")
+ self.aInsFigDash = QAction(self.tr("Figure Dash"), self)
+ self.aInsFigDash.setStatusTip(
+ self.tr("Insert figure dash (same width as a number character)"))
self.aInsFigDash.setShortcut("Ctrl+K, ~")
self.aInsFigDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_FGDASH))
self.mInsDashes.addAction(self.aInsFigDash)
# Insert > Quote Marks
- self.mInsQuotes = self.insertMenu.addMenu("Quote Marks")
+ self.mInsQuotes = self.insertMenu.addMenu(self.tr("Quote Marks"))
# Insert > Left Single Quote
- self.aInsQuoteLS = QAction("Left Single Quote", self)
- self.aInsQuoteLS.setStatusTip("Insert left single quote")
+ self.aInsQuoteLS = QAction(self.tr("Left Single Quote"), self)
+ self.aInsQuoteLS.setStatusTip(self.tr("Insert left single quote"))
self.aInsQuoteLS.setShortcut("Ctrl+K, 1")
self.aInsQuoteLS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LS))
self.mInsQuotes.addAction(self.aInsQuoteLS)
# Insert > Right Single Quote
- self.aInsQuoteRS = QAction("Right Single Quote", self)
- self.aInsQuoteRS.setStatusTip("Insert right single quote")
+ self.aInsQuoteRS = QAction(self.tr("Right Single Quote"), self)
+ self.aInsQuoteRS.setStatusTip(self.tr("Insert right single quote"))
self.aInsQuoteRS.setShortcut("Ctrl+K, 2")
self.aInsQuoteRS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RS))
self.mInsQuotes.addAction(self.aInsQuoteRS)
# Insert > Left Double Quote
- self.aInsQuoteLD = QAction("Left Double Quote", self)
- self.aInsQuoteLD.setStatusTip("Insert left double quote")
+ self.aInsQuoteLD = QAction(self.tr("Left Double Quote"), self)
+ self.aInsQuoteLD.setStatusTip(self.tr("Insert left double quote"))
self.aInsQuoteLD.setShortcut("Ctrl+K, 3")
self.aInsQuoteLD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LD))
self.mInsQuotes.addAction(self.aInsQuoteLD)
# Insert > Right Double Quote
- self.aInsQuoteRD = QAction("Right Double Quote", self)
- self.aInsQuoteRD.setStatusTip("Insert right double quote")
+ self.aInsQuoteRD = QAction(self.tr("Right Double Quote"), self)
+ self.aInsQuoteRD.setStatusTip(self.tr("Insert right double quote"))
self.aInsQuoteRD.setShortcut("Ctrl+K, 4")
self.aInsQuoteRD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RD))
self.mInsQuotes.addAction(self.aInsQuoteRD)
# Insert > Alternative Apostrophe
- self.aInsMSApos = QAction("Alternative Apostrophe", self)
- self.aInsMSApos.setStatusTip("Insert modifier letter single apostrophe")
+ self.aInsMSApos = QAction(self.tr("Alternative Apostrophe"), self)
+ self.aInsMSApos.setStatusTip(self.tr("Insert modifier letter single apostrophe"))
self.aInsMSApos.setShortcut("Ctrl+K, '")
self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwUnicode.U_MAPOSS))
self.mInsQuotes.addAction(self.aInsMSApos)
# Insert > Symbols
- self.mInsPunct = self.insertMenu.addMenu("General Punctuation")
+ self.mInsPunct = self.insertMenu.addMenu(self.tr("General Punctuation"))
# Insert > Ellipsis
- self.aInsEllipsis = QAction("Ellipsis", self)
- self.aInsEllipsis.setStatusTip("Insert ellipsis")
+ self.aInsEllipsis = QAction(self.tr("Ellipsis"), self)
+ self.aInsEllipsis.setStatusTip(self.tr("Insert ellipsis"))
self.aInsEllipsis.setShortcut("Ctrl+K, .")
self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwUnicode.U_HELLIP))
self.mInsPunct.addAction(self.aInsEllipsis)
# Insert > Prime
- self.aInsPrime = QAction("Prime", self)
- self.aInsPrime.setStatusTip("Insert a prime symbol")
+ self.aInsPrime = QAction(self.tr("Prime"), self)
+ self.aInsPrime.setStatusTip(self.tr("Insert a prime symbol"))
self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'")
self.aInsPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_PRIME))
self.mInsPunct.addAction(self.aInsPrime)
# Insert > Double Prime
- self.aInsDPrime = QAction("Double Prime", self)
- self.aInsDPrime.setStatusTip("Insert a double prime symbol")
+ self.aInsDPrime = QAction(self.tr("Double Prime"), self)
+ self.aInsDPrime.setStatusTip(self.tr("Insert a double prime symbol"))
self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"")
self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME))
self.mInsPunct.addAction(self.aInsDPrime)
# Insert > Breaks and Spaces
- self.mInsBreaks = self.insertMenu.addMenu("Breaks and Spaces")
+ self.mInsBreaks = self.insertMenu.addMenu(self.tr("Breaks and Spaces"))
# Insert > Hard Line Break
- self.aInsHardBreak = QAction("Hard Line Break", self)
- self.aInsHardBreak.setStatusTip("Insert a hard line break")
+ self.aInsHardBreak = QAction(self.tr("Hard Line Break"), self)
+ self.aInsHardBreak.setStatusTip(self.tr("Insert a hard line break"))
self.aInsHardBreak.setShortcut("Ctrl+K, Return")
self.aInsHardBreak.triggered.connect(lambda: self._docInsert(nwDocInsert.HARD_BREAK))
self.mInsBreaks.addAction(self.aInsHardBreak)
# Insert > Non-Breaking Space
- self.aInsNBSpace = QAction("Non-Breaking Space", self)
- self.aInsNBSpace.setStatusTip("Insert a non-breaking space")
+ self.aInsNBSpace = QAction(self.tr("Non-Breaking Space"), self)
+ self.aInsNBSpace.setStatusTip(self.tr("Insert a non-breaking space"))
self.aInsNBSpace.setShortcut("Ctrl+K, Space")
self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_NBSP))
self.mInsBreaks.addAction(self.aInsNBSpace)
# Insert > Thin Space
- self.aInsThinSpace = QAction("Thin Space", self)
- self.aInsThinSpace.setStatusTip("Insert a thin space")
+ self.aInsThinSpace = QAction(self.tr("Thin Space"), self)
+ self.aInsThinSpace.setStatusTip(self.tr("Insert a thin space"))
self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space")
self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THSP))
self.mInsBreaks.addAction(self.aInsThinSpace)
# Insert > Thin Non-Breaking Space
- self.aInsThinNBSpace = QAction("Thin Non-Breaking Space", self)
- self.aInsThinNBSpace.setStatusTip("Insert a thin non-breaking space")
+ self.aInsThinNBSpace = QAction(self.tr("Thin Non-Breaking Space"), self)
+ self.aInsThinNBSpace.setStatusTip(self.tr("Insert a thin non-breaking space"))
self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space")
self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THNBSP))
self.mInsBreaks.addAction(self.aInsThinNBSpace)
# Insert > Symbols
- self.mInsSymbol = self.insertMenu.addMenu("Other Symbols")
+ self.mInsSymbol = self.insertMenu.addMenu(self.tr("Other Symbols"))
# Insert > List Bullet
- self.aInsBullet = QAction("List Bullet", self)
- self.aInsBullet.setStatusTip("Insert a list bullet")
+ self.aInsBullet = QAction(self.tr("List Bullet"), self)
+ self.aInsBullet.setStatusTip(self.tr("Insert a list bullet"))
self.aInsBullet.setShortcut("Ctrl+K, *")
self.aInsBullet.triggered.connect(lambda: self._docInsert(nwUnicode.U_BULL))
self.mInsSymbol.addAction(self.aInsBullet)
# Insert > Hyphen Bullet
- self.aInsHyBull = QAction("Hyphen Bullet", self)
- self.aInsHyBull.setStatusTip("Insert a hyphen bullet (alternative bullet)")
+ self.aInsHyBull = QAction(self.tr("Hyphen Bullet"), self)
+ self.aInsHyBull.setStatusTip(self.tr("Insert a hyphen bullet (alternative bullet)"))
self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-")
self.aInsHyBull.triggered.connect(lambda: self._docInsert(nwUnicode.U_HYBULL))
self.mInsSymbol.addAction(self.aInsHyBull)
# Insert > Flower Mark
- self.aInsFlower = QAction("Flower Mark", self)
- self.aInsFlower.setStatusTip("Insert a flower mark (alternative bullet)")
+ self.aInsFlower = QAction(self.tr("Flower Mark"), self)
+ self.aInsFlower.setStatusTip(self.tr("Insert a flower mark (alternative bullet)"))
self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*")
self.aInsFlower.triggered.connect(lambda: self._docInsert(nwUnicode.U_FLOWER))
self.mInsSymbol.addAction(self.aInsFlower)
# Insert > Per Mille
- self.aInsPerMille = QAction("Per Mille", self)
- self.aInsPerMille.setStatusTip("Insert a per mille symbol")
+ self.aInsPerMille = QAction(self.tr("Per Mille"), self)
+ self.aInsPerMille.setStatusTip(self.tr("Insert a per mille symbol"))
self.aInsPerMille.setShortcut("Ctrl+K, %")
self.aInsPerMille.triggered.connect(lambda: self._docInsert(nwUnicode.U_PERMIL))
self.mInsSymbol.addAction(self.aInsPerMille)
# Insert > Degree Symbol
- self.aInsDegree = QAction("Degree Symbol", self)
- self.aInsDegree.setStatusTip("Insert a degree symbol")
+ self.aInsDegree = QAction(self.tr("Degree Symbol"), self)
+ self.aInsDegree.setStatusTip(self.tr("Insert a degree symbol"))
self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O")
self.aInsDegree.triggered.connect(lambda: self._docInsert(nwUnicode.U_DEGREE))
self.mInsSymbol.addAction(self.aInsDegree)
# Insert > Minus Sign
- self.aInsMinus = QAction("Minus Sign", self)
- self.aInsMinus.setStatusTip("Insert a minus sign (not a hypen or dash)")
+ self.aInsMinus = QAction(self.tr("Minus Sign"), self)
+ self.aInsMinus.setStatusTip(self.tr("Insert a minus sign (not a hypen or dash)"))
self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M")
self.aInsMinus.triggered.connect(lambda: self._docInsert(nwUnicode.U_MINUS))
self.mInsSymbol.addAction(self.aInsMinus)
# Insert > Times Sign
- self.aInsTimes = QAction("Times Sign", self)
- self.aInsTimes.setStatusTip("Insert a times sign (multiplication cross)")
+ self.aInsTimes = QAction(self.tr("Times Sign"), self)
+ self.aInsTimes.setStatusTip(self.tr("Insert a times sign (multiplication cross)"))
self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X")
self.aInsTimes.triggered.connect(lambda: self._docInsert(nwUnicode.U_TIMES))
self.mInsSymbol.addAction(self.aInsTimes)
# Insert > Division
- self.aInsDivide = QAction("Division Sign", self)
- self.aInsDivide.setStatusTip("Insert a division sign")
+ self.aInsDivide = QAction(self.tr("Division Sign"), self)
+ self.aInsDivide.setStatusTip(self.tr("Insert a division sign"))
self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D")
self.aInsDivide.triggered.connect(lambda: self._docInsert(nwUnicode.U_DIVIDE))
self.mInsSymbol.addAction(self.aInsDivide)
@@ -724,7 +727,7 @@ class GuiMainMenu(QMenuBar):
self.insertMenu.addSeparator()
# Insert > Tags and References
- self.mInsKeywords = self.insertMenu.addMenu("Tags and References")
+ self.mInsKeywords = self.insertMenu.addMenu(self.tr("Tags and References"))
self.mInsKWItems = {}
self.mInsKWItems[nwKeyWords.TAG_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, G")
self.mInsKWItems[nwKeyWords.POV_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, V")
@@ -737,7 +740,8 @@ class GuiMainMenu(QMenuBar):
self.mInsKWItems[nwKeyWords.ENTITY_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, E")
self.mInsKWItems[nwKeyWords.CUSTOM_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, X")
for n, keyWord in enumerate(self.mInsKWItems):
- self.mInsKWItems[keyWord][0].setText(nwLabels.KEY_NAME[keyWord])
+ self.mInsKWItems[keyWord][0].setText(
+ QCoreApplication.translate("Constant", nwLabels.KEY_NAME[keyWord]))
self.mInsKWItems[keyWord][0].setShortcut(self.mInsKWItems[keyWord][1])
self.mInsKWItems[keyWord][0].triggered.connect(
lambda n, keyWord=keyWord: self._insertKeyWord(keyWord)
@@ -750,18 +754,18 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Search menu.
"""
# Search
- self.srcMenu = self.addMenu("&Search")
+ self.srcMenu = self.addMenu(self.tr("&Search"))
# Search > Find
- self.aFind = QAction("Find", self)
- self.aFind.setStatusTip("Find text in document")
+ self.aFind = QAction(self.tr("Find"), self)
+ self.aFind.setStatusTip(self.tr("Find text in document"))
self.aFind.setShortcut("Ctrl+F")
self.aFind.triggered.connect(lambda: self._docAction(nwDocAction.FIND))
self.srcMenu.addAction(self.aFind)
# Search > Replace
- self.aReplace = QAction("Replace", self)
- self.aReplace.setStatusTip("Replace text in document")
+ self.aReplace = QAction(self.tr("Replace"), self)
+ self.aReplace.setStatusTip(self.tr("Replace text in document"))
if self.mainConf.osDarwin:
self.aReplace.setShortcut("Ctrl+=")
else:
@@ -770,8 +774,8 @@ class GuiMainMenu(QMenuBar):
self.srcMenu.addAction(self.aReplace)
# Search > Find Next
- self.aFindNext = QAction("Find Next", self)
- self.aFindNext.setStatusTip("Find next occurrence text in document")
+ self.aFindNext = QAction(self.tr("Find Next"), self)
+ self.aFindNext.setStatusTip(self.tr("Find next occurrence text in document"))
if self.mainConf.osDarwin:
self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
else:
@@ -780,8 +784,8 @@ class GuiMainMenu(QMenuBar):
self.srcMenu.addAction(self.aFindNext)
# Search > Find Prev
- self.aFindPrev = QAction("Find Previous", self)
- self.aFindPrev.setStatusTip("Find previous occurrence text in document")
+ self.aFindPrev = QAction(self.tr("Find Previous"), self)
+ self.aFindPrev.setStatusTip(self.tr("Find previous occurrence text in document"))
if self.mainConf.osDarwin:
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
else:
@@ -790,8 +794,9 @@ class GuiMainMenu(QMenuBar):
self.srcMenu.addAction(self.aFindPrev)
# Search > Replace Next
- self.aReplaceNext = QAction("Replace Next", self)
- self.aReplaceNext.setStatusTip("Find and replace next occurrence text in document")
+ self.aReplaceNext = QAction(self.tr("Replace Next"), self)
+ self.aReplaceNext.setStatusTip(
+ self.tr("Find and replace next occurrence text in document"))
self.aReplaceNext.setShortcut("Ctrl+Shift+1")
self.aReplaceNext.triggered.connect(lambda: self._docAction(nwDocAction.REPL_NEXT))
self.srcMenu.addAction(self.aReplaceNext)
@@ -802,25 +807,25 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Format menu.
"""
# Format
- self.fmtMenu = self.addMenu("&Format")
+ self.fmtMenu = self.addMenu(self.tr("&Format"))
# Format > Emphasis
- self.aFmtEmph = QAction("Emphasis", self)
- self.aFmtEmph.setStatusTip("Add emphasis to selected text (italic)")
+ self.aFmtEmph = QAction(self.tr("Emphasis"), self)
+ self.aFmtEmph.setStatusTip(self.tr("Add emphasis to selected text (italic)"))
self.aFmtEmph.setShortcut("Ctrl+I")
self.aFmtEmph.triggered.connect(lambda: self._docAction(nwDocAction.EMPH))
self.fmtMenu.addAction(self.aFmtEmph)
# Format > Strong Emphasis
- self.aFmtStrong = QAction("Strong Emphasis", self)
- self.aFmtStrong.setStatusTip("Add strong emphasis to selected text (bold)")
+ self.aFmtStrong = QAction(self.tr("Strong Emphasis"), self)
+ self.aFmtStrong.setStatusTip(self.tr("Add strong emphasis to selected text (bold)"))
self.aFmtStrong.setShortcut("Ctrl+B")
self.aFmtStrong.triggered.connect(lambda: self._docAction(nwDocAction.STRONG))
self.fmtMenu.addAction(self.aFmtStrong)
# Format > Strikethrough
- self.aFmtStrike = QAction("Strikethrough", self)
- self.aFmtStrike.setStatusTip("Add strikethrough to selected text")
+ self.aFmtStrike = QAction(self.tr("Strikethrough"), self)
+ self.aFmtStrike.setStatusTip(self.tr("Add strikethrough to selected text"))
self.aFmtStrike.setShortcut("Ctrl+D")
self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE))
self.fmtMenu.addAction(self.aFmtStrike)
@@ -829,15 +834,15 @@ class GuiMainMenu(QMenuBar):
self.fmtMenu.addSeparator()
# Format > Double Quotes
- self.aFmtDQuote = QAction("Wrap Double Quotes", self)
- self.aFmtDQuote.setStatusTip("Wrap selected text in double quotes")
+ self.aFmtDQuote = QAction(self.tr("Wrap Double Quotes"), self)
+ self.aFmtDQuote.setStatusTip(self.tr("Wrap selected text in double quotes"))
self.aFmtDQuote.setShortcut("Ctrl+\"")
self.aFmtDQuote.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE))
self.fmtMenu.addAction(self.aFmtDQuote)
# Format > Single Quotes
- self.aFmtSQuote = QAction("Wrap Single Quotes", self)
- self.aFmtSQuote.setStatusTip("Wrap selected text in single quotes")
+ self.aFmtSQuote = QAction(self.tr("Wrap Single Quotes"), self)
+ self.aFmtSQuote.setStatusTip(self.tr("Wrap selected text in single quotes"))
self.aFmtSQuote.setShortcut("Ctrl+'")
self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
self.fmtMenu.addAction(self.aFmtSQuote)
@@ -846,43 +851,43 @@ class GuiMainMenu(QMenuBar):
self.fmtMenu.addSeparator()
# Format > Header 1
- self.aFmtHead1 = QAction("Header 1", self)
- self.aFmtHead1.setStatusTip("Change the block format to Header 1")
+ self.aFmtHead1 = QAction(self.tr("Header 1"), self)
+ self.aFmtHead1.setStatusTip(self.tr("Change the block format to Header 1"))
self.aFmtHead1.setShortcut("Ctrl+1")
self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1))
self.fmtMenu.addAction(self.aFmtHead1)
# Format > Header 2
- self.aFmtHead2 = QAction("Header 2", self)
- self.aFmtHead2.setStatusTip("Change the block format to Header 2")
+ self.aFmtHead2 = QAction(self.tr("Header 2"), self)
+ self.aFmtHead2.setStatusTip(self.tr("Change the block format to Header 2"))
self.aFmtHead2.setShortcut("Ctrl+2")
self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2))
self.fmtMenu.addAction(self.aFmtHead2)
# Format > Header 3
- self.aFmtHead3 = QAction("Header 3", self)
- self.aFmtHead3.setStatusTip("Change the block format to Header 3")
+ self.aFmtHead3 = QAction(self.tr("Header 3"), self)
+ self.aFmtHead3.setStatusTip(self.tr("Change the block format to Header 3"))
self.aFmtHead3.setShortcut("Ctrl+3")
self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3))
self.fmtMenu.addAction(self.aFmtHead3)
# Format > Header 4
- self.aFmtHead4 = QAction("Header 4", self)
- self.aFmtHead4.setStatusTip("Change the block format to Header 4")
+ self.aFmtHead4 = QAction(self.tr("Header 4"), self)
+ self.aFmtHead4.setStatusTip(self.tr("Change the block format to Header 4"))
self.aFmtHead4.setShortcut("Ctrl+4")
self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4))
self.fmtMenu.addAction(self.aFmtHead4)
# Format > Comment
- self.aFmtComment = QAction("Comment", self)
- self.aFmtComment.setStatusTip("Change the block format to comment")
+ self.aFmtComment = QAction(self.tr("Comment"), self)
+ self.aFmtComment.setStatusTip(self.tr("Change the block format to comment"))
self.aFmtComment.setShortcut("Ctrl+/")
self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM))
self.fmtMenu.addAction(self.aFmtComment)
# Format > Remove Block Format
- self.aFmtNoFormat = QAction("Remove Block Format", self)
- self.aFmtNoFormat.setStatusTip("Strips block format")
+ self.aFmtNoFormat = QAction(self.tr("Remove Block Format"), self)
+ self.aFmtNoFormat.setStatusTip(self.tr("Strips block format"))
self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"])
self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
self.fmtMenu.addAction(self.aFmtNoFormat)
@@ -891,14 +896,16 @@ class GuiMainMenu(QMenuBar):
self.fmtMenu.addSeparator()
# Format > Replace Single Quotes
- self.aFmtReplSng = QAction("Replace Single Quotes", self)
- self.aFmtReplSng.setStatusTip("Replace all straight single quotes in selected text")
+ self.aFmtReplSng = QAction(self.tr("Replace Single Quotes"), self)
+ self.aFmtReplSng.setStatusTip(
+ self.tr("Replace all straight single quotes in selected text"))
self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
self.fmtMenu.addAction(self.aFmtReplSng)
# Format > Replace Double Quotes
- self.aFmtReplDbl = QAction("Replace Double Quotes", self)
- self.aFmtReplDbl.setStatusTip("Replace all straight double quotes in selected text")
+ self.aFmtReplDbl = QAction(self.tr("Replace Double Quotes"), self)
+ self.aFmtReplDbl.setStatusTip(
+ self.tr("Replace all straight double quotes in selected text"))
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
self.fmtMenu.addAction(self.aFmtReplDbl)
@@ -908,11 +915,11 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Tools menu.
"""
# Tools
- self.toolsMenu = self.addMenu("&Tools")
+ self.toolsMenu = self.addMenu(self.tr("&Tools"))
# Tools > Check Spelling
- self.aSpellCheck = QAction("Check Spelling", self)
- self.aSpellCheck.setStatusTip("Toggle check spelling")
+ self.aSpellCheck = QAction(self.tr("Check Spelling"), self)
+ self.aSpellCheck.setStatusTip(self.tr("Toggle check spelling"))
self.aSpellCheck.setCheckable(True)
self.aSpellCheck.setChecked(self.theProject.spellCheck)
self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
@@ -920,15 +927,15 @@ class GuiMainMenu(QMenuBar):
self.toolsMenu.addAction(self.aSpellCheck)
# Tools > Re-Run Spell Check
- self.aReRunSpell = QAction("Re-Run Spell Check", self)
- self.aReRunSpell.setStatusTip("Run the spell checker on current document")
+ self.aReRunSpell = QAction(self.tr("Re-Run Spell Check"), self)
+ self.aReRunSpell.setStatusTip(self.tr("Run the spell checker on current document"))
self.aReRunSpell.setShortcut("F7")
self.aReRunSpell.triggered.connect(lambda: self.theParent.docEditor.spellCheckDocument())
self.toolsMenu.addAction(self.aReRunSpell)
# Tools > Project Word List
- self.aEditWordList = QAction("Project Word List", self)
- self.aEditWordList.setStatusTip("Edit the project's word list")
+ self.aEditWordList = QAction(self.tr("Project Word List"), self)
+ self.aEditWordList.setStatusTip(self.tr("Edit the project's word list"))
self.aEditWordList.triggered.connect(lambda: self.theParent.showProjectWordListDialog())
self.toolsMenu.addAction(self.aEditWordList)
@@ -936,22 +943,23 @@ class GuiMainMenu(QMenuBar):
self.toolsMenu.addSeparator()
# Tools > Rebuild Indices
- self.aRebuildIndex = QAction("Rebuild Index", self)
- self.aRebuildIndex.setStatusTip("Rebuild the tag indices and word counts")
+ self.aRebuildIndex = QAction(self.tr("Rebuild Index"), self)
+ self.aRebuildIndex.setStatusTip(self.tr("Rebuild the tag indices and word counts"))
self.aRebuildIndex.setShortcut("F9")
self.aRebuildIndex.triggered.connect(lambda: self.theParent.rebuildIndex())
self.toolsMenu.addAction(self.aRebuildIndex)
# Tools > Rebuild Outline
- self.aRebuildOutline = QAction("Rebuild Outline", self)
- self.aRebuildOutline.setStatusTip("Rebuild the novel outline tree")
+ self.aRebuildOutline = QAction(self.tr("Rebuild Outline"), self)
+ self.aRebuildOutline.setStatusTip(self.tr("Rebuild the novel outline tree"))
self.aRebuildOutline.setShortcut("F10")
self.aRebuildOutline.triggered.connect(lambda: self.theParent.rebuildOutline())
self.toolsMenu.addAction(self.aRebuildOutline)
# Tools > Toggle Auto Build Outline
- self.aAutoOutline = QAction("Auto-Update Outline", self)
- self.aAutoOutline.setStatusTip("Update project outline when a novel file is changed")
+ self.aAutoOutline = QAction(self.tr("Auto-Update Outline"), self)
+ self.aAutoOutline.setStatusTip(self.tr(
+ "Update project outline when a novel file is changed"))
self.aAutoOutline.setCheckable(True)
self.aAutoOutline.toggled.connect(self._toggleAutoOutline)
self.aAutoOutline.setShortcut("Ctrl+F10")
@@ -961,28 +969,28 @@ class GuiMainMenu(QMenuBar):
self.toolsMenu.addSeparator()
# Tools > Backup
- self.aBackupProject = QAction("Backup Project Folder", self)
- self.aBackupProject.setStatusTip("Backup Project")
+ self.aBackupProject = QAction(self.tr("Backup Project Folder"), self)
+ self.aBackupProject.setStatusTip(self.tr("Backup Project"))
self.aBackupProject.triggered.connect(lambda: self.theProject.zipIt(True))
self.toolsMenu.addAction(self.aBackupProject)
# Tools > Export Project
- self.aBuildProject = QAction("Build Novel Project", self)
- self.aBuildProject.setStatusTip("Launch the Build novel project tool")
+ self.aBuildProject = QAction(self.tr("Build Novel Project"), self)
+ self.aBuildProject.setStatusTip(self.tr("Launch the Build novel project tool"))
self.aBuildProject.setShortcut("F5")
self.aBuildProject.triggered.connect(lambda: self.theParent.showBuildProjectDialog())
self.toolsMenu.addAction(self.aBuildProject)
# Tools > Writing Stats
- self.aWritingStats = QAction("Writing Statistics", self)
- self.aWritingStats.setStatusTip("Show the writing statistics dialog")
+ self.aWritingStats = QAction(self.tr("Writing Statistics"), self)
+ self.aWritingStats.setStatusTip(self.tr("Show the writing statistics dialog"))
self.aWritingStats.setShortcut("F6")
self.aWritingStats.triggered.connect(lambda: self.theParent.showWritingStatsDialog())
self.toolsMenu.addAction(self.aWritingStats)
# Tools > Settings
- self.aPreferences = QAction("Preferences", self)
- self.aPreferences.setStatusTip("Preferences")
+ self.aPreferences = QAction(self.tr("Preferences"), self)
+ self.aPreferences.setStatusTip(self.tr("Preferences"))
self.aPreferences.setShortcut("Ctrl+,")
self.aPreferences.setMenuRole(QAction.PreferencesRole)
self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog())
@@ -994,18 +1002,18 @@ class GuiMainMenu(QMenuBar):
"""Assemble the Help menu.
"""
# Help
- self.helpMenu = self.addMenu("&Help")
+ self.helpMenu = self.addMenu(self.tr("&Help"))
# Help > About
- self.aAboutNW = QAction("About novelWriter", self)
- self.aAboutNW.setStatusTip("About novelWriter")
+ self.aAboutNW = QAction(self.tr("About novelWriter"), self)
+ self.aAboutNW.setStatusTip(self.tr("About novelWriter"))
self.aAboutNW.setMenuRole(QAction.AboutRole)
self.aAboutNW.triggered.connect(lambda: self.theParent.showAboutNWDialog())
self.helpMenu.addAction(self.aAboutNW)
# Help > About Qt5
- self.aAboutQt = QAction("About Qt5", self)
- self.aAboutQt.setStatusTip("About Qt5")
+ self.aAboutQt = QAction(self.tr("About Qt5"), self)
+ self.aAboutQt.setStatusTip(self.tr("About Qt5"))
self.aAboutQt.setMenuRole(QAction.AboutQtRole)
self.aAboutQt.triggered.connect(lambda: self.theParent.showAboutQtDialog())
self.helpMenu.addAction(self.aAboutQt)
@@ -1015,14 +1023,15 @@ class GuiMainMenu(QMenuBar):
# Document > Documentation
if self.mainConf.hasHelp and self.mainConf.hasAssistant:
- self.aHelpLoc = QAction("Documentation (Local)", self)
- self.aHelpLoc.setStatusTip("View local documentation with Qt Assistant")
+ self.aHelpLoc = QAction(self.tr("Documentation (Local)"), self)
+ self.aHelpLoc.setStatusTip(self.tr("View local documentation with Qt Assistant"))
self.aHelpLoc.triggered.connect(self._openAssistant)
self.aHelpLoc.setShortcut("F1")
self.helpMenu.addAction(self.aHelpLoc)
- self.aHelpWeb = QAction("Documentation (Online)", self)
- self.aHelpWeb.setStatusTip("View online documentation at %s" % nw.__docurl__)
+ self.aHelpWeb = QAction(self.tr("Documentation (Online)"), self)
+ self.aHelpWeb.setStatusTip(
+ self.tr("View online documentation at {0}").format(nw.__docurl__))
self.aHelpWeb.triggered.connect(lambda: self._openWebsite(nw.__docurl__))
if self.mainConf.hasHelp and self.mainConf.hasAssistant:
self.aHelpWeb.setShortcut("Shift+F1")
@@ -1034,26 +1043,30 @@ class GuiMainMenu(QMenuBar):
self.helpMenu.addSeparator()
# Document > Report an Issue
- self.aIssue = QAction("Report an Issue (GitHub)", self)
- self.aIssue.setStatusTip("Report a bug or issue on GitHub at %s" % nw.__issuesurl__)
+ self.aIssue = QAction(self.tr("Report an Issue (GitHub)"), self)
+ self.aIssue.setStatusTip(
+ self.tr("Report a bug or issue on GitHub at {0}").format(nw.__issuesurl__))
self.aIssue.triggered.connect(lambda: self._openWebsite(nw.__issuesurl__))
self.helpMenu.addAction(self.aIssue)
# Document > Ask a Question
- self.aQuestion = QAction("Ask a Question (GitHub)", self)
- self.aQuestion.setStatusTip("Ask a question on GitHub at %s" % nw.__helpurl__)
+ self.aQuestion = QAction(self.tr("Ask a Question (GitHub)"), self)
+ self.aQuestion.setStatusTip(
+ self.tr("Ask a question on GitHub at {0}").format(nw.__helpurl__))
self.aQuestion.triggered.connect(lambda: self._openWebsite(nw.__helpurl__))
self.helpMenu.addAction(self.aQuestion)
# Document > Latest Release
- self.aRelease = QAction("Latest Release (GitHub)", self)
- self.aRelease.setStatusTip("Open the Releases page on GitHub at %s" % nw.__releaseurl__)
+ self.aRelease = QAction(self.tr("Latest Release (GitHub)"), self)
+ self.aRelease.setStatusTip(
+ self.tr("Open the Releases page on GitHub at {0}").format(nw.__releaseurl__))
self.aRelease.triggered.connect(lambda: self._openWebsite(nw.__releaseurl__))
self.helpMenu.addAction(self.aRelease)
# Document > Main Website
- self.aWebsite = QAction("The novelWriter Website", self)
- self.aWebsite.setStatusTip("Open the novelWriter website at %s" % nw.__url__)
+ self.aWebsite = QAction(self.tr("The novelWriter Website"), self)
+ self.aWebsite.setStatusTip(
+ self.tr("Open the novelWriter website at {0}").format(nw.__url__))
self.aWebsite.triggered.connect(lambda: self._openWebsite(nw.__url__))
self.helpMenu.addAction(self.aWebsite)
diff --git a/nw/gui/noveltree.py b/nw/gui/noveltree.py
index 0ceaf9ec..7dde6aeb 100644
--- a/nw/gui/noveltree.py
+++ b/nw/gui/noveltree.py
@@ -63,7 +63,11 @@ class GuiNovelTree(QTreeWidget):
self.setIconSize(QSize(iPx, iPx))
self.setIndentation(iPx)
self.setColumnCount(3)
- self.setHeaderLabels(["Title", "Words", "POV"])
+ self.setHeaderLabels([
+ self.tr("Title"),
+ self.tr("Words"),
+ self.tr("POV")
+ ])
self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._itemSelected)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -73,9 +77,9 @@ class GuiNovelTree(QTreeWidget):
treeHeadItem = self.headerItem()
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
- treeHeadItem.setToolTip(self.C_TITLE, "Section title")
- treeHeadItem.setToolTip(self.C_WORDS, "Word count")
- treeHeadItem.setToolTip(self.C_POV, "Point-of-view character")
+ treeHeadItem.setToolTip(self.C_TITLE, self.tr("Section title"))
+ treeHeadItem.setToolTip(self.C_WORDS, self.tr("Word count"))
+ treeHeadItem.setToolTip(self.C_POV, self.tr("Point-of-view character"))
treeHeader = self.header()
treeHeader.setStretchLastSection(True)
diff --git a/nw/gui/outline.py b/nw/gui/outline.py
index 666c8026..fac7de53 100644
--- a/nw/gui/outline.py
+++ b/nw/gui/outline.py
@@ -29,7 +29,7 @@ import logging
from time import time
-from PyQt5.QtCore import Qt, QSize
+from PyQt5.QtCore import QCoreApplication, Qt, QSize
from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView
)
@@ -149,7 +149,8 @@ class GuiOutline(QTreeWidget):
"""
self.clear()
self.setColumnCount(1)
- self.setHeaderLabel(nwLabels.OUTLINE_COLS[nwOutline.TITLE])
+ self.setHeaderLabel(
+ QCoreApplication.translate("Constant", nwLabels.OUTLINE_COLS[nwOutline.TITLE]))
self.treeOrder = []
self.colWidth = {}
@@ -355,7 +356,8 @@ class GuiOutline(QTreeWidget):
if self.firstView:
theLabels = []
for i, hItem in enumerate(self.treeOrder):
- theLabels.append(nwLabels.OUTLINE_COLS[hItem])
+ theLabels.append(
+ QCoreApplication.translate("Constant", nwLabels.OUTLINE_COLS[hItem]))
self.colIndex[hItem] = i
self.setHeaderLabels(theLabels)
@@ -474,7 +476,7 @@ class GuiOutlineHeaderMenu(QMenu):
self.theParent = theParent
self.acceptToggle = True
- mnuHead = QAction("Select Columns", self)
+ mnuHead = QAction(self.tr("Select Columns"), self)
self.addAction(mnuHead)
self.addSeparator()
@@ -482,7 +484,8 @@ class GuiOutlineHeaderMenu(QMenu):
for hItem in nwOutline:
if hItem == nwOutline.TITLE:
continue
- self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self)
+ self.actionMap[hItem] = QAction(
+ QCoreApplication.translate("Constant", nwLabels.OUTLINE_COLS[hItem]), self)
self.actionMap[hItem].setCheckable(True)
self.actionMap[hItem].toggled.connect(
lambda isChecked, tItem=hItem : self._columnToggled(isChecked, tItem)
diff --git a/nw/gui/outlinedetails.py b/nw/gui/outlinedetails.py
index 15785ff8..ab83ccee 100644
--- a/nw/gui/outlinedetails.py
+++ b/nw/gui/outlinedetails.py
@@ -27,7 +27,7 @@ along with this program. If not, see .
import nw
import logging
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, Qt
from PyQt5.QtWidgets import (
QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel
)
@@ -40,10 +40,10 @@ logger = logging.getLogger(__name__)
class GuiOutlineDetails(QScrollArea):
LVL_MAP = {
- "H1" : "Title",
- "H2" : "Chapter",
- "H3" : "Scene",
- "H4" : "Section"
+ "H1" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
+ "H2" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"),
+ "H3" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"),
+ "H4" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Section"),
}
def __init__(self, theParent):
@@ -66,9 +66,9 @@ class GuiOutlineDetails(QScrollArea):
vSpace = int(self.mainConf.pxInt(4))
# Details Area
- self.titleLabel = QLabel("Title")
- self.fileLabel = QLabel("Document")
- self.itemLabel = QLabel("Status")
+ self.titleLabel = QLabel("%s" % self.tr("Title"))
+ self.fileLabel = QLabel("%s" % self.tr("Document"))
+ self.itemLabel = QLabel("%s" % self.tr("Status"))
self.titleValue = QLabel("")
self.fileValue = QLabel("")
self.itemValue = QLabel("")
@@ -81,9 +81,9 @@ class GuiOutlineDetails(QScrollArea):
self.itemValue.setMaximumWidth(maxTitle)
# Stats Area
- self.cCLabel = QLabel("Characters")
- self.wCLabel = QLabel("Words")
- self.pCLabel = QLabel("Paragraphs")
+ self.cCLabel = QLabel("%s" % self.tr("Characters"))
+ self.wCLabel = QLabel("%s" % self.tr("Words"))
+ self.pCLabel = QLabel("%s" % self.tr("Paragraphs"))
self.cCValue = QLabel("")
self.wCValue = QLabel("")
self.pCValue = QLabel("")
@@ -96,7 +96,7 @@ class GuiOutlineDetails(QScrollArea):
self.pCValue.setAlignment(Qt.AlignRight)
# Synopsis
- self.synopLabel = QLabel("Synopsis")
+ self.synopLabel = QLabel("%s" % self.tr("Synopsis"))
self.synopValue = QLabel("")
self.synopLWrap = QHBoxLayout()
self.synopValue.setWordWrap(True)
@@ -104,15 +104,24 @@ class GuiOutlineDetails(QScrollArea):
self.synopLWrap.addWidget(self.synopValue, 1)
# Tags
- self.povKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
- self.focKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
- self.chrKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY])
- self.pltKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
- self.timKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.TIME_KEY])
- self.wldKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY])
- self.objKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY])
- self.entKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY])
- self.cstKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY])
+ self.povKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.POV_KEY]))
+ self.focKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]))
+ self.chrKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]))
+ self.pltKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]))
+ self.timKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]))
+ self.wldKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]))
+ self.objKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]))
+ self.entKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]))
+ self.cstKeyLabel = QLabel("%s" % QCoreApplication.translate(
+ "Constant", nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]))
self.povKeyLWrap = QHBoxLayout()
self.focKeyLWrap = QHBoxLayout()
@@ -165,7 +174,7 @@ class GuiOutlineDetails(QScrollArea):
self.cstKeyLWrap.addWidget(self.cstKeyValue, 1)
# Selected Item Details
- self.mainGroup = QGroupBox("Title Details", self)
+ self.mainGroup = QGroupBox(self.tr("Title Details"), self)
self.mainForm = QGridLayout()
self.mainGroup.setLayout(self.mainForm)
@@ -190,7 +199,7 @@ class GuiOutlineDetails(QScrollArea):
self.mainForm.setVerticalSpacing(vSpace)
# Selected Item Tags
- self.tagsGroup = QGroupBox("Reference Tags", self)
+ self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
self.tagsForm = QGridLayout()
self.tagsGroup.setLayout(self.tagsForm)
@@ -256,7 +265,7 @@ class GuiOutlineDetails(QScrollArea):
def clearDetails(self):
"""Clear all the data labels.
"""
- self.titleLabel.setText("Title")
+ self.titleLabel.setText("%s" % self.tr("Title"))
self.titleValue.setText("")
self.fileValue.setText("")
self.itemValue.setText("")
@@ -286,9 +295,9 @@ class GuiOutlineDetails(QScrollArea):
return False
if novIdx["level"] in self.LVL_MAP:
- self.titleLabel.setText("%s" % self.LVL_MAP[novIdx["level"]])
+ self.titleLabel.setText("%s" % self.tr(self.LVL_MAP[novIdx["level"]]))
else:
- self.titleLabel.setText("Title")
+ self.titleLabel.setText("%s" % self.tr("Title"))
self.titleValue.setText(novIdx["title"])
self.fileValue.setText(nwItem.itemName)
diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py
index 4913be25..e2ddfaae 100644
--- a/nw/gui/preferences.py
+++ b/nw/gui/preferences.py
@@ -28,7 +28,7 @@ import nw
import logging
import os
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import QCoreApplication, Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import (
QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
@@ -53,7 +53,7 @@ class GuiPreferences(PagedDialog):
self.theParent = theParent
self.theProject = theProject
- self.setWindowTitle("Preferences")
+ self.setWindowTitle(self.tr("Preferences"))
self.tabGeneral = GuiPreferencesGeneral(self.theParent)
self.tabProjects = GuiPreferencesProjects(self.theParent)
@@ -62,14 +62,16 @@ class GuiPreferences(PagedDialog):
self.tabSyntax = GuiPreferencesSyntax(self.theParent)
self.tabAuto = GuiPreferencesAutomation(self.theParent)
- self.addTab(self.tabGeneral, "General")
- self.addTab(self.tabProjects, "Projects")
- self.addTab(self.tabDocs, "Documents")
- self.addTab(self.tabEditor, "Editor")
- self.addTab(self.tabSyntax, "Highlighting")
- self.addTab(self.tabAuto, "Automation")
+ self.addTab(self.tabGeneral, self.tr("General"))
+ self.addTab(self.tabProjects, self.tr("Projects"))
+ self.addTab(self.tabDocs, self.tr("Documents"))
+ self.addTab(self.tabEditor, self.tr("Editor"))
+ self.addTab(self.tabSyntax, self.tr("Highlighting"))
+ 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.Cancel).setText(self.tr("Cancel"))
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox)
@@ -98,7 +100,7 @@ class GuiPreferences(PagedDialog):
if needsRestart:
self.theParent.makeAlert(
- "Some changes will not be applied until novelWriter has been restarted.",
+ self.tr("Some changes will not be applied until novelWriter has been restarted."),
nwAlert.INFO
)
@@ -130,7 +132,7 @@ class GuiPreferencesGeneral(QWidget):
# Look and Feel
# =============
- self.mainForm.addGroupLabel("Look and Feel")
+ self.mainForm.addGroupLabel(self.tr("Look and Feel"))
## Select Theme
self.guiTheme = QComboBox()
@@ -143,9 +145,9 @@ class GuiPreferencesGeneral(QWidget):
self.guiTheme.setCurrentIndex(themeIdx)
self.mainForm.addRow(
- "Main GUI theme",
+ self.tr("Main GUI theme"),
self.guiTheme,
- "Changing this requires restarting novelWriter."
+ self.tr("Changing this requires restarting novelWriter.")
)
## Select Icon Theme
@@ -159,18 +161,18 @@ class GuiPreferencesGeneral(QWidget):
self.guiIcons.setCurrentIndex(iconIdx)
self.mainForm.addRow(
- "Main icon theme",
+ self.tr("Main icon theme"),
self.guiIcons,
- "Changing this requires restarting novelWriter."
+ self.tr("Changing this requires restarting novelWriter.")
)
## Dark Icons
self.guiDark = QSwitch()
self.guiDark.setChecked(self.mainConf.guiDark)
self.mainForm.addRow(
- "Prefer icons for dark backgrounds",
+ self.tr("Prefer icons for dark backgrounds"),
self.guiDark,
- "May improve the look of icons on dark themes."
+ self.tr("May improve the look of icons on dark themes.")
)
## Font Family
@@ -182,9 +184,9 @@ class GuiPreferencesGeneral(QWidget):
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
self.fontButton.clicked.connect(self._selectFont)
self.mainForm.addRow(
- "Font family",
+ self.tr("Font family"),
self.guiFont,
- "Changing this requires restarting novelWriter.",
+ self.tr("Changing this requires restarting novelWriter."),
theButton = self.fontButton
)
@@ -195,38 +197,38 @@ class GuiPreferencesGeneral(QWidget):
self.guiFontSize.setSingleStep(1)
self.guiFontSize.setValue(self.mainConf.guiFontSize)
self.mainForm.addRow(
- "Font size",
+ self.tr("Font size"),
self.guiFontSize,
- "Changing this requires restarting novelWriter.",
+ self.tr("Changing this requires restarting novelWriter."),
theUnit = "pt"
)
# GUI Settings
# ============
- self.mainForm.addGroupLabel("GUI Settings")
+ self.mainForm.addGroupLabel(self.tr("GUI Settings"))
self.showFullPath = QSwitch()
self.showFullPath.setChecked(self.mainConf.showFullPath)
self.mainForm.addRow(
- "Show full path in document header",
+ self.tr("Show full path in document header"),
self.showFullPath,
- "Add the parent folder names to the header."
+ self.tr("Add the parent folder names to the header.")
)
self.hideVScroll = QSwitch()
self.hideVScroll.setChecked(self.mainConf.hideVScroll)
self.mainForm.addRow(
- "Hide vertical scroll bars in main windows",
+ self.tr("Hide vertical scroll bars in main windows"),
self.hideVScroll,
- "Scrolling available with mouse wheel and keys only."
+ self.tr("Scrolling available with mouse wheel and keys only.")
)
self.hideHScroll = QSwitch()
self.hideHScroll.setChecked(self.mainConf.hideHScroll)
self.mainForm.addRow(
- "Hide horizontal scroll bars in main windows",
+ self.tr("Hide horizontal scroll bars in main windows"),
self.hideHScroll,
- "Scrolling available with mouse wheel and keys only."
+ self.tr("Scrolling available with mouse wheel and keys only.")
)
return
@@ -295,7 +297,7 @@ class GuiPreferencesProjects(QWidget):
# Automatic Save
# ==============
- self.mainForm.addGroupLabel("Automatic Save")
+ self.mainForm.addGroupLabel(self.tr("Automatic Save"))
## Document Save Timer
self.autoSaveDoc = QSpinBox(self)
@@ -304,10 +306,10 @@ class GuiPreferencesProjects(QWidget):
self.autoSaveDoc.setSingleStep(1)
self.autoSaveDoc.setValue(self.mainConf.autoSaveDoc)
self.mainForm.addRow(
- "Save document interval",
+ self.tr("Save document interval"),
self.autoSaveDoc,
- "How often the open document is automatically saved.",
- theUnit="seconds"
+ self.tr("How often the open document is automatically saved."),
+ theUnit=self.tr("seconds")
)
## Project Save Timer
@@ -317,24 +319,24 @@ class GuiPreferencesProjects(QWidget):
self.autoSaveProj.setSingleStep(1)
self.autoSaveProj.setValue(self.mainConf.autoSaveProj)
self.mainForm.addRow(
- "Save project interval",
+ self.tr("Save project interval"),
self.autoSaveProj,
- "How often the open project is automatically saved.",
- theUnit="seconds"
+ self.tr("How often the open project is automatically saved."),
+ theUnit=self.tr("seconds")
)
# Project Backup
# ==============
- self.mainForm.addGroupLabel("Project Backup")
+ self.mainForm.addGroupLabel(self.tr("Project Backup"))
## Backup Path
self.backupPath = self.mainConf.backupPath
- self.backupGetPath = QPushButton("Browse")
+ self.backupGetPath = QPushButton(self.tr("Browse"))
self.backupGetPath.clicked.connect(self._backupFolder)
self.backupPathRow = self.mainForm.addRow(
- "Backup storage location",
+ self.tr("Backup storage location"),
self.backupGetPath,
- "Path: %s" % self.backupPath
+ self.tr("{0}: {1}").format(self.tr("Path"), self.backupPath)
)
## Run when closing
@@ -342,9 +344,9 @@ class GuiPreferencesProjects(QWidget):
self.backupOnClose.setChecked(self.mainConf.backupOnClose)
self.backupOnClose.toggled.connect(self._toggledBackupOnClose)
self.mainForm.addRow(
- "Run backup when the project is closed",
+ self.tr("Run backup when the project is closed"),
self.backupOnClose,
- "Can be overridden for individual projects in project settings."
+ self.tr("Can be overridden for individual projects in project settings.")
)
## Ask before backup
@@ -353,22 +355,22 @@ class GuiPreferencesProjects(QWidget):
self.askBeforeBackup.setChecked(self.mainConf.askBeforeBackup)
self.askBeforeBackup.setEnabled(self.mainConf.backupOnClose)
self.mainForm.addRow(
- "Ask before running backup",
+ self.tr("Ask before running backup"),
self.askBeforeBackup,
- "If off, backups will run in the background."
+ self.tr("If off, backups will run in the background.")
)
# Session Timer
# =============
- self.mainForm.addGroupLabel("Session Timer")
+ self.mainForm.addGroupLabel(self.tr("Session Timer"))
## Pause when idle
self.stopWhenIdle = QSwitch()
self.stopWhenIdle.setChecked(self.mainConf.stopWhenIdle)
self.mainForm.addRow(
- "Pause the session timer when not writing",
+ self.tr("Pause the session timer when not writing"),
self.stopWhenIdle,
- "Also pauses when the application window does not have focus."
+ self.tr("Also pauses when the application window does not have focus.")
)
## Inactive time for idle
@@ -379,10 +381,10 @@ class GuiPreferencesProjects(QWidget):
self.userIdleTime.setDecimals(1)
self.userIdleTime.setValue(self.mainConf.userIdleTime/60.0)
self.mainForm.addRow(
- "Editor inactive time before pausing timer",
+ self.tr("Editor inactive time before pausing timer"),
self.userIdleTime,
- "User activity includes typing and changing the content.",
- theUnit="minutes"
+ self.tr("User activity includes typing and changing the content."),
+ theUnit=self.tr("minutes")
)
return
@@ -422,11 +424,12 @@ class GuiPreferencesProjects(QWidget):
dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog
newDir = QFileDialog.getExistingDirectory(
- self, "Backup Directory", currDir, options=dlgOpt
+ self, self.tr("Backup Directory"), currDir, options=dlgOpt
)
if newDir:
self.backupPath = newDir
- self.mainForm.setHelpText(self.backupPathRow, "Path: %s" % self.backupPath)
+ self.mainForm.setHelpText(
+ self.backupPathRow, self.tr("{0}: {1}").format(self.tr("Path"), self.backupPath))
return True
return False
@@ -456,7 +459,7 @@ class GuiPreferencesDocuments(QWidget):
# Text Style
# ==========
- self.mainForm.addGroupLabel("Text Style")
+ self.mainForm.addGroupLabel(self.tr("Text Style"))
## Font Family
self.textFont = QLineEdit()
@@ -467,9 +470,9 @@ class GuiPreferencesDocuments(QWidget):
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
self.fontButton.clicked.connect(self._selectFont)
self.mainForm.addRow(
- "Font family",
+ self.tr("Font family"),
self.textFont,
- "Font for the document editor and viewer.",
+ self.tr("Font for the document editor and viewer."),
theButton = self.fontButton
)
@@ -480,15 +483,15 @@ class GuiPreferencesDocuments(QWidget):
self.textSize.setSingleStep(1)
self.textSize.setValue(self.mainConf.textSize)
self.mainForm.addRow(
- "Font size",
+ self.tr("Font size"),
self.textSize,
- "Font size for the document editor and viewer.",
+ self.tr("Font size for the document editor and viewer."),
theUnit = "pt"
)
# Text Flow
# =========
- self.mainForm.addGroupLabel("Text Flow")
+ self.mainForm.addGroupLabel(self.tr("Text Flow"))
## Max Text Width in Normal Mode
self.textWidth = QSpinBox(self)
@@ -497,10 +500,10 @@ class GuiPreferencesDocuments(QWidget):
self.textWidth.setSingleStep(10)
self.textWidth.setValue(self.mainConf.textWidth)
self.mainForm.addRow(
- "Maximum text width in \"Normal Mode\"",
+ self.tr("Maximum text width in \"Normal Mode\""),
self.textWidth,
- "Horizontal margins are scaled automatically.",
- theUnit="px"
+ self.tr("Horizontal margins are scaled automatically."),
+ theUnit=self.tr("px")
)
## Max Text Width in Focus Mode
@@ -510,37 +513,37 @@ class GuiPreferencesDocuments(QWidget):
self.focusWidth.setSingleStep(10)
self.focusWidth.setValue(self.mainConf.focusWidth)
self.mainForm.addRow(
- "Maximum text width in \"Focus Mode\"",
+ self.tr("Maximum text width in \"Focus Mode\""),
self.focusWidth,
- "Horizontal margins are scaled automatically.",
- theUnit="px"
+ self.tr("Horizontal margins are scaled automatically."),
+ theUnit=self.tr("px")
)
## Document Fixed Width
self.textFixedW = QSwitch()
self.textFixedW.setChecked(not self.mainConf.textFixedW)
self.mainForm.addRow(
- "Disable maximum text width in \"Normal Mode\"",
+ self.tr("Disable maximum text width in \"Normal Mode\""),
self.textFixedW,
- "Text width is defined by the margins only."
+ self.tr("Text width is defined by the margins only.")
)
## Focus Mode Footer
self.hideFocusFooter = QSwitch()
self.hideFocusFooter.setChecked(self.mainConf.hideFocusFooter)
self.mainForm.addRow(
- "Hide document footer in \"Focus Mode\"",
+ self.tr("Hide document footer in \"Focus Mode\""),
self.hideFocusFooter,
- "Hide the information bar at the bottom of the document."
+ self.tr("Hide the information bar at the bottom of the document.")
)
## Justify Text
self.doJustify = QSwitch()
self.doJustify.setChecked(self.mainConf.doJustify)
self.mainForm.addRow(
- "Justify the text margins in editor and viewer",
+ self.tr("Justify the text margins in editor and viewer"),
self.doJustify,
- "Lay out text with straight edges in the editor and viewer."
+ self.tr("Lay out text with straight edges in the editor and viewer.")
)
## Document Margins
@@ -550,10 +553,10 @@ class GuiPreferencesDocuments(QWidget):
self.textMargin.setSingleStep(1)
self.textMargin.setValue(self.mainConf.textMargin)
self.mainForm.addRow(
- "Text margin",
+ self.tr("Text margin"),
self.textMargin,
- "If maximum width is set, this becomes the minimum margin.",
- theUnit="px"
+ self.tr("If maximum width is set, this becomes the minimum margin."),
+ theUnit=self.tr("px")
)
## Tab Width
@@ -563,10 +566,10 @@ class GuiPreferencesDocuments(QWidget):
self.tabWidth.setSingleStep(1)
self.tabWidth.setValue(self.mainConf.tabWidth)
self.mainForm.addRow(
- "Tab width",
+ self.tr("Tab width"),
self.tabWidth,
- "The width of a tab key press in the editor and viewer.",
- theUnit="px"
+ self.tr("The width of a tab key press in the editor and viewer."),
+ theUnit=self.tr("px")
)
return
@@ -626,13 +629,17 @@ class GuiPreferencesEditor(QWidget):
# Spell Checking
# ==============
- self.mainForm.addGroupLabel("Spell Checking")
+ self.mainForm.addGroupLabel(self.tr("Spell Checking"))
## Spell Check Provider and Language
self.spellLangList = QComboBox(self)
self.spellToolList = QComboBox(self)
- self.spellToolList.addItem("Internal (difflib)", nwConst.SP_INTERNAL)
- self.spellToolList.addItem("Spell Enchant (pyenchant)", nwConst.SP_ENCHANT)
+ self.spellToolList.addItem(
+ self.tr("{0} ({1})").format(self.tr("Internal"), "difflib"),
+ QCoreApplication.translate("Constant", nwConst.SP_INTERNAL))
+ self.spellToolList.addItem(
+ self.tr("{0} ({1})").format(self.tr("Spell Enchant"), "pyenchant"),
+ QCoreApplication.translate("Constant", nwConst.SP_ENCHANT))
theModel = self.spellToolList.model()
idEnchant = self.spellToolList.findData(nwConst.SP_ENCHANT)
@@ -645,14 +652,14 @@ class GuiPreferencesEditor(QWidget):
self._doUpdateSpellTool(0)
self.mainForm.addRow(
- "Spell check provider",
+ self.tr("Spell check provider"),
self.spellToolList,
- "Note that the internal spell check tool is quite slow."
+ self.tr("Note that the internal spell check tool is quite slow.")
)
self.mainForm.addRow(
- "Spell check language",
+ self.tr("Spell check language"),
self.spellLangList,
- "Available languages are determined by your system."
+ self.tr("Available languages are determined by your system.")
)
## Big Document Size Limit
@@ -662,15 +669,15 @@ class GuiPreferencesEditor(QWidget):
self.bigDocLimit.setSingleStep(10)
self.bigDocLimit.setValue(self.mainConf.bigDocLimit)
self.mainForm.addRow(
- "Big document limit",
+ self.tr("Big document limit"),
self.bigDocLimit,
- "Full spell checking is disabled above this limit.",
- theUnit="kB"
+ self.tr("Full spell checking is disabled above this limit."),
+ theUnit=self.tr("kB")
)
# Word Count
# ==========
- self.mainForm.addGroupLabel("Word Count")
+ self.mainForm.addGroupLabel(self.tr("Word Count"))
## Word Count Timer
self.wordCountTimer = QDoubleSpinBox(self)
@@ -680,54 +687,54 @@ class GuiPreferencesEditor(QWidget):
self.wordCountTimer.setSingleStep(0.1)
self.wordCountTimer.setValue(self.mainConf.wordCountTimer)
self.mainForm.addRow(
- "Word count interval",
+ self.tr("Word count interval"),
self.wordCountTimer,
- "How often the word count is updated.",
- theUnit="seconds"
+ self.tr("How often the word count is updated."),
+ theUnit=self.tr("seconds")
)
# Writing Guides
# ==============
- self.mainForm.addGroupLabel("Writing Guides")
+ self.mainForm.addGroupLabel(self.tr("Writing Guides"))
## Show Tabs and Spaces
self.showTabsNSpaces = QSwitch()
self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces)
self.mainForm.addRow(
- "Show tabs and spaces",
+ self.tr("Show tabs and spaces"),
self.showTabsNSpaces,
- "Add symbols to indicate tabs and spaces in the editor."
+ self.tr("Add symbols to indicate tabs and spaces in the editor.")
)
## Show Line Endings
self.showLineEndings = QSwitch()
self.showLineEndings.setChecked(self.mainConf.showLineEndings)
self.mainForm.addRow(
- "Show line endings",
+ self.tr("Show line endings"),
self.showLineEndings,
- "Add a symbol to indicate line endings in the editor."
+ self.tr("Add a symbol to indicate line endings in the editor.")
)
# Scroll Behaviour
# ================
- self.mainForm.addGroupLabel("Scroll Behaviour")
+ self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))
## Scroll Past End
self.scrollPastEnd = QSwitch()
self.scrollPastEnd.setChecked(self.mainConf.scrollPastEnd)
self.mainForm.addRow(
- "Scroll past end of the document",
+ self.tr("Scroll past end of the document"),
self.scrollPastEnd,
- "Also improves trypewriter scrolling for short documents."
+ self.tr("Also improves trypewriter scrolling for short documents.")
)
## Typewriter Scrolling
self.autoScroll = QSwitch()
self.autoScroll.setChecked(self.mainConf.autoScroll)
self.mainForm.addRow(
- "Typewriter style scrolling when you type",
+ self.tr("Typewriter style scrolling when you type"),
self.autoScroll,
- "Try to keep the cursor at a fixed vertical position."
+ self.tr("Try to keep the cursor at a fixed vertical position.")
)
## Typewriter Position
@@ -737,9 +744,9 @@ class GuiPreferencesEditor(QWidget):
self.autoScrollPos.setSingleStep(1)
self.autoScrollPos.setValue(int(self.mainConf.autoScrollPos))
self.mainForm.addRow(
- "Minimum position for Typewriter scrolling",
+ self.tr("Minimum position for Typewriter scrolling"),
self.autoScrollPos,
- "Percentage of the editor height from the top.",
+ self.tr("Percentage of the editor height from the top."),
theUnit = "%"
)
@@ -819,7 +826,7 @@ class GuiPreferencesSyntax(QWidget):
# Highlighting Theme
# ==================
- self.mainForm.addGroupLabel("Highlighting Theme")
+ self.mainForm.addGroupLabel(self.tr("Highlighting Theme"))
self.guiSyntax = QComboBox()
self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
@@ -831,50 +838,50 @@ class GuiPreferencesSyntax(QWidget):
self.guiSyntax.setCurrentIndex(syntaxIdx)
self.mainForm.addRow(
- "Highlighting theme",
+ self.tr("Highlighting theme"),
self.guiSyntax,
- "Colour theme to apply to the editor and viewer."
+ self.tr("Colour theme to apply to the editor and viewer.")
)
# Quotes & Dialogue
# =================
- self.mainForm.addGroupLabel("Quotes & Dialogue")
+ self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))
self.highlightQuotes = QSwitch()
self.highlightQuotes.setChecked(self.mainConf.highlightQuotes)
self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes)
self.mainForm.addRow(
- "Highlight text wrapped in quotes",
+ self.tr("Highlight text wrapped in quotes"),
self.highlightQuotes,
- "Applies to single, double and straight quotes."
+ self.tr("Applies to single, double and straight quotes.")
)
self.allowOpenSQuote = QSwitch()
self.allowOpenSQuote.setChecked(self.mainConf.allowOpenSQuote)
self.mainForm.addRow(
- "Allow open-ended single quotes",
+ self.tr("Allow open-ended single quotes"),
self.allowOpenSQuote,
- "Highlight single-quoted line with no closing quote."
+ self.tr("Highlight single-quoted line with no closing quote.")
)
self.allowOpenDQuote = QSwitch()
self.allowOpenDQuote.setChecked(self.mainConf.allowOpenDQuote)
self.mainForm.addRow(
- "Allow open-ended double quotes",
+ self.tr("Allow open-ended double quotes"),
self.allowOpenDQuote,
- "Highlight double-quoted line with no closing quote."
+ self.tr("Highlight double-quoted line with no closing quote.")
)
# Text Emphasis
# =============
- self.mainForm.addGroupLabel("Text Emphasis")
+ self.mainForm.addGroupLabel(self.tr("Text Emphasis"))
self.highlightEmph = QSwitch()
self.highlightEmph.setChecked(self.mainConf.highlightEmph)
self.mainForm.addRow(
- "Add highlight colour to emphasised text",
+ self.tr("Add highlight colour to emphasised text"),
self.highlightEmph,
- "Applies to emphasis (italic) and strong (bold)."
+ self.tr("Applies to emphasis (italic) and strong (bold).")
)
return
@@ -927,15 +934,15 @@ class GuiPreferencesAutomation(QWidget):
# Automatic Features
# ==================
- self.mainForm.addGroupLabel("Automatic Features")
+ self.mainForm.addGroupLabel(self.tr("Automatic Features"))
## Auto-Select Word Under Cursor
self.autoSelect = QSwitch()
self.autoSelect.setChecked(self.mainConf.autoSelect)
self.mainForm.addRow(
- "Auto-select word under cursor",
+ self.tr("Auto-select word under cursor"),
self.autoSelect,
- "Apply formatting to word under cursor if no selection is made."
+ self.tr("Apply formatting to word under cursor if no selection is made.")
)
## Auto-Replace as You Type Main Switch
@@ -943,23 +950,23 @@ class GuiPreferencesAutomation(QWidget):
self.doReplace.setChecked(self.mainConf.doReplace)
self.doReplace.toggled.connect(self._toggleAutoReplaceMain)
self.mainForm.addRow(
- "Auto-replace text as you type",
+ self.tr("Auto-replace text as you type"),
self.doReplace,
- "Allow the editor to replace symbols as you type."
+ self.tr("Allow the editor to replace symbols as you type.")
)
# Replace as You Type
# ===================
- self.mainForm.addGroupLabel("Replace as You Type")
+ self.mainForm.addGroupLabel(self.tr("Replace as You Type"))
## Auto-Replace Single Quotes
self.doReplaceSQuote = QSwitch()
self.doReplaceSQuote.setChecked(self.mainConf.doReplaceSQuote)
self.doReplaceSQuote.setEnabled(self.mainConf.doReplace)
self.mainForm.addRow(
- "Auto-replace single quotes",
+ self.tr("Auto-replace single quotes"),
self.doReplaceSQuote,
- "Try to guess which is an opening or a closing single quote."
+ self.tr("Try to guess which is an opening or a closing single quote.")
)
## Auto-Replace Double Quotes
@@ -967,9 +974,9 @@ class GuiPreferencesAutomation(QWidget):
self.doReplaceDQuote.setChecked(self.mainConf.doReplaceDQuote)
self.doReplaceDQuote.setEnabled(self.mainConf.doReplace)
self.mainForm.addRow(
- "Auto-replace double quotes",
+ self.tr("Auto-replace double quotes"),
self.doReplaceDQuote,
- "Try to guess which is an opening or a closing double quote."
+ self.tr("Try to guess which is an opening or a closing double quote.")
)
## Auto-Replace Hyphens
@@ -977,9 +984,9 @@ class GuiPreferencesAutomation(QWidget):
self.doReplaceDash.setChecked(self.mainConf.doReplaceDash)
self.doReplaceDash.setEnabled(self.mainConf.doReplace)
self.mainForm.addRow(
- "Auto-replace dashes",
+ self.tr("Auto-replace dashes"),
self.doReplaceDash,
- "Double and triple hyphens become short and long dashes."
+ self.tr("Double and triple hyphens become short and long dashes.")
)
## Auto-Replace Dots
@@ -987,14 +994,14 @@ class GuiPreferencesAutomation(QWidget):
self.doReplaceDots.setChecked(self.mainConf.doReplaceDots)
self.doReplaceDots.setEnabled(self.mainConf.doReplace)
self.mainForm.addRow(
- "Auto-replace dots",
+ self.tr("Auto-replace dots"),
self.doReplaceDots,
- "Three consecutive dots become ellipsis."
+ self.tr("Three consecutive dots become ellipsis.")
)
# Quotation Style
# ===============
- self.mainForm.addGroupLabel("Quotation Style")
+ self.mainForm.addGroupLabel(self.tr("Quotation Style"))
qWidth = self.mainConf.pxInt(40)
bWidth = int(2.5*self.theTheme.getTextWidth("..."))
@@ -1011,9 +1018,9 @@ class GuiPreferencesAutomation(QWidget):
self.btnSingleStyleO.setMaximumWidth(bWidth)
self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO"))
self.mainForm.addRow(
- "Single quote open style",
+ self.tr("Single quote open style"),
self.quoteSym["SO"],
- "The symbol to use for a leading single quote.",
+ self.tr("The symbol to use for a leading single quote."),
theButton=self.btnSingleStyleO
)
@@ -1027,9 +1034,9 @@ class GuiPreferencesAutomation(QWidget):
self.btnSingleStyleC.setMaximumWidth(bWidth)
self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC"))
self.mainForm.addRow(
- "Single quote close style",
+ self.tr("Single quote close style"),
self.quoteSym["SC"],
- "The symbol to use for a trailing single quote.",
+ self.tr("The symbol to use for a trailing single quote."),
theButton=self.btnSingleStyleC
)
@@ -1044,9 +1051,9 @@ class GuiPreferencesAutomation(QWidget):
self.btnDoubleStyleO.setMaximumWidth(bWidth)
self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO"))
self.mainForm.addRow(
- "Double quote open style",
+ self.tr("Double quote open style"),
self.quoteSym["DO"],
- "The symbol to use for a leading double quote.",
+ self.tr("The symbol to use for a leading double quote."),
theButton=self.btnDoubleStyleO
)
@@ -1060,9 +1067,9 @@ class GuiPreferencesAutomation(QWidget):
self.btnDoubleStyleC.setMaximumWidth(bWidth)
self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC"))
self.mainForm.addRow(
- "Double quote close style",
+ self.tr("Double quote close style"),
self.quoteSym["DC"],
- "The symbol to use for a trailing double quote.",
+ self.tr("The symbol to use for a trailing double quote."),
theButton=self.btnDoubleStyleC
)
diff --git a/nw/gui/projdetails.py b/nw/gui/projdetails.py
index d37cebee..9e7e1029 100644
--- a/nw/gui/projdetails.py
+++ b/nw/gui/projdetails.py
@@ -54,7 +54,7 @@ class GuiProjectDetails(PagedDialog):
self.theProject = theProject
self.optState = theProject.optState
- self.setWindowTitle("Project Details")
+ self.setWindowTitle(self.tr("Project Details"))
wW = self.mainConf.pxInt(600)
wH = self.mainConf.pxInt(400)
@@ -69,10 +69,11 @@ class GuiProjectDetails(PagedDialog):
self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject)
self.tabContents = GuiProjectDetailsContents(self.theParent, self.theProject)
- self.addTab(self.tabMain, "Overview")
- self.addTab(self.tabContents, "Contents")
+ self.addTab(self.tabMain, self.tr("Overview"))
+ self.addTab(self.tabContents, self.tr("Contents"))
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
+ self.buttonBox.button(QDialogButtonBox.Close).setText(self.tr("Close"))
self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox)
@@ -154,7 +155,8 @@ class GuiProjectDetailsMain(QWidget):
self.bookTitle.setAlignment(Qt.AlignHCenter)
self.bookTitle.setWordWrap(True)
- self.projName = QLabel("Working Title: %s" % self.theProject.projName)
+ self.projName = QLabel(self.tr("{0}: {1}").format(
+ self.tr("Working Title"), self.theProject.projName))
workFont = self.projName.font()
workFont.setPointSizeF(0.8*fPt)
workFont.setItalic(True)
@@ -162,7 +164,7 @@ class GuiProjectDetailsMain(QWidget):
self.projName.setAlignment(Qt.AlignHCenter)
self.projName.setWordWrap(True)
- self.bookAuthors = QLabel("By %s" % self.theProject.getAuthors())
+ self.bookAuthors = QLabel(self.tr("By {0}").format(self.theProject.getAuthors()))
authFont = self.bookAuthors.font()
authFont.setPointSizeF(1.2*fPt)
self.bookAuthors.setFont(authFont)
@@ -175,20 +177,20 @@ class GuiProjectDetailsMain(QWidget):
hCounts = self.theIndex.getNovelTitleCounts()
nwCount = self.theIndex.getNovelWordCount()
- self.wordCountLbl = QLabel("Words:")
+ self.wordCountLbl = QLabel("%s:" % self.tr("Words"))
self.wordCountVal = QLabel(f"{nwCount:n}")
- self.chapCountLbl = QLabel("Chapters:")
+ self.chapCountLbl = QLabel("%s:" % self.tr("Chapters"))
self.chapCountVal = QLabel(f"{hCounts[2]:n}")
- self.sceneCountLbl = QLabel("Scenes:")
+ self.sceneCountLbl = QLabel("%s:" % self.tr("Scenes"))
self.sceneCountVal = QLabel(f"{hCounts[3]:n}")
- self.revCountLbl = QLabel("Revisions:")
+ self.revCountLbl = QLabel("%s:" % self.tr("Revisions"))
self.revCountVal = QLabel(f"{self.theProject.saveCount:n}")
edTime = self.theProject.getCurrentEditTime()
- self.editTimeLbl = QLabel("Editing Time:")
+ self.editTimeLbl = QLabel("%s:" % self.tr("Editing Time"))
self.editTimeVal = QLabel(f"{edTime//3600:02d}:{edTime%3600//60:02d}")
self.statsGrid = QGridLayout()
@@ -208,7 +210,7 @@ class GuiProjectDetailsMain(QWidget):
# Meta
# ====
- self.projPathLbl = QLabel("Path:")
+ self.projPathLbl = QLabel("%s:" % self.tr("Path"))
self.projPathVal = QLineEdit()
self.projPathVal.setText(self.theProject.projPath)
self.projPathVal.setReadOnly(True)
@@ -271,7 +273,13 @@ class GuiProjectDetailsContents(QWidget):
self.tocTree.setIndentation(0)
self.tocTree.setColumnCount(6)
self.tocTree.setSelectionMode(QAbstractItemView.NoSelection)
- self.tocTree.setHeaderLabels(["Title", "Words", "Pages", "Page", "Progress", ""])
+ self.tocTree.setHeaderLabels(
+ [self.tr("Title"),
+ self.tr("Words"),
+ self.tr("Pages"),
+ self.tr("Page"),
+ self.tr("Progress"),
+ ""])
treeHeadItem = self.tocTree.headerItem()
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
@@ -304,16 +312,16 @@ class GuiProjectDetailsContents(QWidget):
clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble", True)
wordsHelp = (
- "Typical word count for a 5 by 8 inch book page with 11 pt font is 350."
+ self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.")
)
offsetHelp = (
- "Start counting page numbers from this page."
+ self.tr("Start counting page numbers from this page.")
)
dblHelp = (
- "Assume a new chapter or partition always start on an odd numbered page."
+ self.tr("Assume a new chapter or partition always start on an odd numbered page.")
)
- self.wpLabel = QLabel("Words per page")
+ self.wpLabel = QLabel(self.tr("Words per page"))
self.wpLabel.setToolTip(wordsHelp)
self.wpValue = QSpinBox()
@@ -324,7 +332,7 @@ class GuiProjectDetailsContents(QWidget):
self.wpValue.setToolTip(wordsHelp)
self.wpValue.valueChanged.connect(self._populateTree)
- self.poLabel = QLabel("Count pages from")
+ self.poLabel = QLabel(self.tr("Count pages from"))
self.poLabel.setToolTip(offsetHelp)
self.poValue = QSpinBox()
@@ -335,7 +343,7 @@ class GuiProjectDetailsContents(QWidget):
self.poValue.setToolTip(offsetHelp)
self.poValue.valueChanged.connect(self._populateTree)
- self.dblLabel = QLabel("Clear double pages")
+ self.dblLabel = QLabel(self.tr("Clear double pages"))
self.dblLabel.setToolTip(dblHelp)
self.dblValue = QSwitch(self, 2*iPx, iPx)
@@ -358,7 +366,7 @@ class GuiProjectDetailsContents(QWidget):
# ========
self.outerBox = QVBoxLayout()
- self.outerBox.addWidget(QLabel("Table of Contents"))
+ self.outerBox.addWidget(QLabel("%s" % self.tr("Table of Contents")))
self.outerBox.addWidget(self.tocTree)
self.outerBox.addLayout(self.optionsBox)
@@ -390,7 +398,7 @@ class GuiProjectDetailsContents(QWidget):
"""
self._theToC = []
self._theToC = self.theIndex.getTableOfContents(2)
- self._theToC.append(("", 0, "END", 0))
+ self._theToC.append(("", 0, self.tr("END"), 0))
return
##
diff --git a/nw/gui/projload.py b/nw/gui/projload.py
index 29401de3..8579d194 100644
--- a/nw/gui/projload.py
+++ b/nw/gui/projload.py
@@ -74,7 +74,7 @@ class GuiProjectLoad(QDialog):
self.outerBox.setSpacing(sPx)
self.innerBox.setSpacing(sPx)
- self.setWindowTitle("Open Project")
+ self.setWindowTitle(self.tr("Open Project"))
self.setMinimumWidth(self.mainConf.pxInt(650))
self.setMinimumHeight(self.mainConf.pxInt(400))
self.setModal(True)
@@ -90,7 +90,11 @@ class GuiProjectLoad(QDialog):
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
self.listBox.setColumnCount(3)
- self.listBox.setHeaderLabels(["Working Title", "Words", "Last Opened"])
+ self.listBox.setHeaderLabels([
+ self.tr("Working Title"),
+ self.tr("Words"),
+ self.tr("Last Opened"),
+ ])
self.listBox.setRootIsDecorated(False)
self.listBox.itemSelectionChanged.connect(self._doSelectRecent)
self.listBox.itemDoubleClicked.connect(self._doOpenRecent)
@@ -100,8 +104,8 @@ class GuiProjectLoad(QDialog):
treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight)
treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight)
- self.lblRecent = QLabel("Recently Opened Projects")
- self.lblPath = QLabel("Path")
+ self.lblRecent = QLabel("%s" % self.tr("Recently Opened Projects"))
+ self.lblPath = QLabel("%s" % self.tr("Path"))
self.selPath = QLineEdit("")
self.selPath.setReadOnly(True)
@@ -123,13 +127,15 @@ class GuiProjectLoad(QDialog):
self.innerBox.addLayout(self.projectForm)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
+ self.buttonBox.button(QDialogButtonBox.Open).setText(self.tr("Open"))
+ self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel"))
self.buttonBox.accepted.connect(self._doOpenRecent)
self.buttonBox.rejected.connect(self._doCancel)
- self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole)
+ self.newButton = self.buttonBox.addButton(self.tr("New"), QDialogButtonBox.ActionRole)
self.newButton.clicked.connect(self._doNewProject)
- self.delButton = self.buttonBox.addButton("Remove", QDialogButtonBox.ActionRole)
+ self.delButton = self.buttonBox.addButton(self.tr("Remove"), QDialogButtonBox.ActionRole)
self.delButton.clicked.connect(self._doDeleteRecent)
self.outerBox.addLayout(self.innerBox)
@@ -183,8 +189,12 @@ class GuiProjectLoad(QDialog):
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
projFile, _ = QFileDialog.getOpenFileName(
- self, "Open novelWriter Project", "",
- "novelWriter Project File (%s);;All Files (*)" % nwFiles.PROJ_FILE,
+ self, self.tr("Open novelWriter Project"), "",
+ ";;".join([
+ self.tr("{0} ({1})").format(
+ self.tr("novelWriter Project File"), nwFiles.PROJ_FILE),
+ self.tr("{0} ({1})").format(self.tr("All Files"), "*")
+ ]),
options=dlgOpt
)
if projFile:
@@ -221,10 +231,11 @@ class GuiProjectLoad(QDialog):
selList = self.listBox.selectedItems()
if selList:
projName = selList[0].text(self.C_NAME)
- msgYes = self.theParent.askQuestion("Remove Entry", (
- "Remove '%s' from the recent projects list? "
- "The project files will not be deleted."
- ) % projName)
+ msgYes = self.theParent.askQuestion(
+ self.tr("Remove Entry"),
+ self.tr("Remove '{0}' from the recent projects list? "
+ "The project files will not be deleted.").format(projName)
+ )
if msgYes:
self.mainConf.removeFromRecentCache(
selList[0].data(self.C_NAME, Qt.UserRole)
diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py
index e6a4e485..71cc7043 100644
--- a/nw/gui/projsettings.py
+++ b/nw/gui/projsettings.py
@@ -54,7 +54,7 @@ class GuiProjectSettings(PagedDialog):
self.optState = theProject.optState
self.theProject.countStatus()
- self.setWindowTitle("Project Settings")
+ self.setWindowTitle(self.tr("Project Settings"))
wW = self.mainConf.pxInt(570)
wH = self.mainConf.pxInt(375)
@@ -71,12 +71,14 @@ class GuiProjectSettings(PagedDialog):
self.tabImport = GuiProjectEditStatus(self.theParent, self.theProject, False)
self.tabReplace = GuiProjectEditReplace(self.theParent, self.theProject)
- self.addTab(self.tabMain, "Settings")
- self.addTab(self.tabStatus, "Status")
- self.addTab(self.tabImport, "Importance")
- self.addTab(self.tabReplace, "Auto-Replace")
+ self.addTab(self.tabMain, self.tr("Settings"))
+ self.addTab(self.tabStatus, self.tr("Status"))
+ self.addTab(self.tabImport, self.tr("Importance"))
+ 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.Cancel).setText(self.tr("Cancel"))
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox)
@@ -166,7 +168,7 @@ class GuiProjectEditMain(QWidget):
self.mainForm.setHelpTextStyle(self.theParent.theTheme.helpText)
self.setLayout(self.mainForm)
- self.mainForm.addGroupLabel("Project Settings")
+ self.mainForm.addGroupLabel(self.tr("Project Settings"))
xW = self.mainConf.pxInt(250)
xH = self.mainConf.pxInt(100)
@@ -176,9 +178,9 @@ class GuiProjectEditMain(QWidget):
self.editName.setFixedWidth(xW)
self.editName.setText(self.theProject.projName)
self.mainForm.addRow(
- "Working title",
+ self.tr("Working title"),
self.editName,
- "Should be set only once."
+ self.tr("Should be set only once.")
)
self.editTitle = QLineEdit()
@@ -186,9 +188,9 @@ class GuiProjectEditMain(QWidget):
self.editTitle.setFixedWidth(xW)
self.editTitle.setText(self.theProject.bookTitle)
self.mainForm.addRow(
- "Novel title",
+ self.tr("Novel title"),
self.editTitle,
- "Change whenever you want!"
+ self.tr("Change whenever you want!")
)
self.editAuthors = QPlainTextEdit()
@@ -199,22 +201,22 @@ class GuiProjectEditMain(QWidget):
self.editAuthors.setFixedHeight(xH)
self.editAuthors.setFixedWidth(xW)
self.mainForm.addRow(
- "Author(s)",
+ self.tr("Author(s)"),
self.editAuthors,
- "One name per line."
+ self.tr("One name per line.")
)
self.spellLang = QComboBox(self)
theDict = self.theParent.docEditor.theDict
- self.spellLang.addItem("Default", "None")
+ self.spellLang.addItem(self.tr("Default"), "None")
if theDict is not None:
for spTag, spName in theDict.listDictionaries():
self.spellLang.addItem(spName, spTag)
self.mainForm.addRow(
- "Spell check language",
+ self.tr("Spell check language"),
self.spellLang,
- "Overrides main preferences."
+ self.tr("Overrides main preferences.")
)
spellIdx = 0
@@ -226,9 +228,9 @@ class GuiProjectEditMain(QWidget):
self.doBackup = QSwitch(self)
self.doBackup.setChecked(not self.theProject.doBackup)
self.mainForm.addRow(
- "No backup on close",
+ self.tr("No backup on close"),
self.doBackup,
- "Overrides main preferences."
+ self.tr("Overrides main preferences.")
)
return
@@ -271,12 +273,12 @@ class GuiProjectEditStatus(QWidget):
self.editName = QLineEdit()
self.editName.setMaxLength(40)
self.editName.setEnabled(False)
- self.newButton = QPushButton("New")
- self.delButton = QPushButton("Delete")
- self.saveButton = QPushButton("Save")
+ self.newButton = QPushButton(self.tr("New"))
+ self.delButton = QPushButton(self.tr("Delete"))
+ self.saveButton = QPushButton(self.tr("Save"))
self.colPixmap = QPixmap(self.iPx, self.iPx)
self.colPixmap.fill(QColor(120, 120, 120))
- self.colButton = QPushButton(QIcon(self.colPixmap), "Colour")
+ self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
self.colButton.setIconSize(self.colPixmap.rect().size())
self.newButton.clicked.connect(self._newItem)
@@ -287,7 +289,7 @@ class GuiProjectEditStatus(QWidget):
self.mainForm.addWidget(self.newButton)
self.mainForm.addWidget(self.delButton)
self.mainForm.addStretch(1)
- self.mainForm.addWidget(QLabel("Name"))
+ self.mainForm.addWidget(QLabel("%s" % self.tr("Name")))
self.mainForm.addWidget(self.editName)
self.mainForm.addWidget(self.colButton)
self.mainForm.addStretch(1)
@@ -297,9 +299,9 @@ class GuiProjectEditStatus(QWidget):
self.mainBox.addLayout(self.mainForm)
if isStatus:
- self.outerBox.addWidget(QLabel("Novel File Status Levels"))
+ self.outerBox.addWidget(QLabel("%s" % self.tr("Novel File Status Levels")))
else:
- self.outerBox.addWidget(QLabel("Note File Importance Levels"))
+ self.outerBox.addWidget(QLabel("%s" % self.tr("Note File Importance Levels")))
self.outerBox.addLayout(self.mainBox)
self.setLayout(self.outerBox)
@@ -325,7 +327,10 @@ class GuiProjectEditStatus(QWidget):
"""
if self.selColour is not None:
newCol = QColorDialog.getColor(
- self.selColour, self, "Select Colour", QColorDialog.DontUseNativeDialog
+ self.selColour,
+ self,
+ self.tr("Select Colour"),
+ QColorDialog.DontUseNativeDialog
)
if newCol.isValid():
self.selColour = newCol
@@ -338,7 +343,7 @@ class GuiProjectEditStatus(QWidget):
def _newItem(self):
"""Create a new status item.
"""
- newItem = self._addItem("New Item", (0, 0, 0), None, 0)
+ newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0)
newItem.setBackground(QBrush(QColor(0, 255, 0, 80)))
self.colChanged = True
return
@@ -355,7 +360,7 @@ class GuiProjectEditStatus(QWidget):
self.colChanged = True
else:
self.theParent.makeAlert(
- "Cannot delete status item that is in use.", nwAlert.ERROR
+ self.tr("Cannot delete status item that is in use."), nwAlert.ERROR
)
return
@@ -372,7 +377,8 @@ class GuiProjectEditStatus(QWidget):
self.selColour.blue(),
self.colData[selIdx][4]
)
- selItem.setText("%s [%d]" % (self.colData[selIdx][0], self.colCounts[selIdx]))
+ selItem.setText(self.tr("{0} [{1}]").format(
+ self.colData[selIdx][0], self.colCounts[selIdx]))
selItem.setIcon(self.colButton.icon())
self.editName.setEnabled(False)
self.colChanged = True
@@ -384,7 +390,7 @@ class GuiProjectEditStatus(QWidget):
newIcon = QPixmap(self.iPx, self.iPx)
newIcon.fill(QColor(*iCol))
newItem = QListWidgetItem()
- newItem.setText("%s [%d]" % (iName, nUse))
+ newItem.setText(self.tr("{0} [{1}]").format(iName, nUse))
newItem.setIcon(QIcon(newIcon))
newItem.setData(Qt.UserRole, len(self.colData))
self.listBox.addItem(newItem)
@@ -450,13 +456,16 @@ class GuiProjectEditReplace(QWidget):
self.optState.getInt("GuiProjectSettings", "replaceColW", 100)
)
self.listBox = QTreeWidget()
- self.listBox.setHeaderLabels(["Keyword", "Replace With"])
+ self.listBox.setHeaderLabels([
+ self.tr("Keyword"),
+ self.tr("Replace With"),
+ ])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(0, wCol0)
self.listBox.setIndentation(0)
for aKey, aVal in self.theProject.autoReplace.items():
- newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
+ newItem = QTreeWidgetItem([self.tr("<{0}>").format(aKey), aVal])
self.listBox.addTopLevelItem(newItem)
self.listBox.sortByColumn(0, Qt.AscendingOrder)
@@ -467,9 +476,9 @@ class GuiProjectEditReplace(QWidget):
self.saveButton = QPushButton(self.theTheme.getIcon("done"), "")
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
- self.saveButton.setToolTip("Save entry")
- self.addButton.setToolTip("Add new entry")
- self.delButton.setToolTip("Delete selected entry")
+ self.saveButton.setToolTip(self.tr("Save entry"))
+ self.addButton.setToolTip(self.tr("Add new entry"))
+ self.delButton.setToolTip(self.tr("Delete selected entry"))
self.editKey.setEnabled(False)
self.editKey.setMaxLength(40)
@@ -486,7 +495,8 @@ class GuiProjectEditReplace(QWidget):
self.bottomBox.addWidget(self.addButton)
self.bottomBox.addWidget(self.delButton)
- self.outerBox.addWidget(QLabel("Text Replace List for Preview and Export"))
+ self.outerBox.addWidget(
+ QLabel("%s" % self.tr("Text Replace List for Preview and Export")))
self.outerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.bottomBox)
self.setLayout(self.outerBox)
@@ -538,7 +548,7 @@ class GuiProjectEditReplace(QWidget):
saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0:
- selItem.setText(0, "<%s>" % saveKey)
+ selItem.setText(0, self.tr("<{0}>").format(saveKey))
selItem.setText(1, newVal)
self.editKey.clear()
self.editValue.clear()
diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py
index eff1ae7c..02b8228c 100644
--- a/nw/gui/projtree.py
+++ b/nw/gui/projtree.py
@@ -30,7 +30,7 @@ import logging
from time import time
-from PyQt5.QtCore import Qt, QSize, pyqtSignal
+from PyQt5.QtCore import QCoreApplication, Qt, QSize, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
@@ -85,14 +85,19 @@ class GuiProjectTree(QTreeWidget):
self.setExpandsOnDoubleClick(True)
self.setIndentation(iPx)
self.setColumnCount(4)
- self.setHeaderLabels(["Label", "Words", "Inc", "Flags"])
+ self.setHeaderLabels([
+ self.tr("Label"),
+ self.tr("Words"),
+ self.tr("Inc"),
+ self.tr("Flags")
+ ])
treeHeadItem = self.headerItem()
treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
- treeHeadItem.setToolTip(self.C_NAME, "Item label")
- treeHeadItem.setToolTip(self.C_COUNT, "Word count")
- treeHeadItem.setToolTip(self.C_EXPORT, "Include in build")
- treeHeadItem.setToolTip(self.C_FLAGS, "Status, class, and layout flags")
+ treeHeadItem.setToolTip(self.C_NAME, self.tr("Item label"))
+ treeHeadItem.setToolTip(self.C_COUNT, self.tr("Word count"))
+ treeHeadItem.setToolTip(self.C_EXPORT, self.tr("Include in build"))
+ treeHeadItem.setToolTip(self.C_FLAGS, self.tr("Status, class, and layout flags"))
# Let the last column stretch, and set the minimum size to the
# size of the icon as the default Qt font metrics approach fails
@@ -193,12 +198,12 @@ class GuiProjectTree(QTreeWidget):
if itemClass is None:
if itemType == nwItemType.FILE:
self.makeAlert(
- "Please select a valid location in the tree to add the document.",
+ self.tr("Please select a valid location in the tree to add the document."),
nwAlert.ERROR
)
else:
self.makeAlert(
- "Please select a valid location in the tree to add the folder.",
+ self.tr("Please select a valid location in the tree to add the folder."),
nwAlert.ERROR
)
return False
@@ -209,7 +214,8 @@ class GuiProjectTree(QTreeWidget):
)
if itemType == nwItemType.ROOT:
- tHandle = self.theProject.newRoot(nwLabels.CLASS_NAME[itemClass], itemClass)
+ tHandle = self.theProject.newRoot(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[itemClass]), itemClass)
if tHandle is None:
logger.error("No root item added")
return False
@@ -223,7 +229,7 @@ class GuiProjectTree(QTreeWidget):
# If still nothing, give up
if pHandle is None:
self.makeAlert(
- "Did not find anywhere to add the file or folder!", nwAlert.ERROR
+ self.tr("Did not find anywhere to add the file or folder!"), nwAlert.ERROR
)
return False
@@ -237,14 +243,15 @@ class GuiProjectTree(QTreeWidget):
# If we again have no home, give up
if pHandle is None:
self.makeAlert(
- "Did not find anywhere to add the file or folder!", nwAlert.ERROR
+ self.tr("Did not find anywhere to add the file or folder!"), nwAlert.ERROR
)
return False
if self.theProject.projTree.isTrashRoot(pHandle):
self.makeAlert(
- "Cannot add new files or folders to the %s folder." % (
- nwLabels.CLASS_NAME[nwItemClass.TRASH]
+ self.tr("Cannot add new files or folders to the {0} folder.").format(
+ QCoreApplication.translate(
+ "Constant", nwLabels.CLASS_NAME[nwItemClass.TRASH])
), nwAlert.ERROR
)
return False
@@ -253,18 +260,18 @@ class GuiProjectTree(QTreeWidget):
# If we're still here, add the file or folder
if itemType == nwItemType.FILE:
- tHandle = self.theProject.newFile("New File", itemClass, pHandle)
+ tHandle = self.theProject.newFile(self.tr("New File"), itemClass, pHandle)
elif itemType == nwItemType.FOLDER:
if len(parTree) >= nwConst.MAX_DEPTH - 1:
# Folders cannot be deeper than MAX_DEPTH - 1, leaving room
# for one more level of files.
self.makeAlert((
- "Cannot add new folder to this item. "
- "Maximum folder depth has been reached."
+ self.tr("Cannot add new folder to this item."),
+ self.tr("Maximum folder depth has been reached.")
), nwAlert.ERROR)
return False
- tHandle = self.theProject.newFolder("New Folder", itemClass, pHandle)
+ tHandle = self.theProject.newFolder(self.tr("New Folder"), itemClass, pHandle)
else:
logger.error("Failed to add new item")
@@ -428,7 +435,7 @@ class GuiProjectTree(QTreeWidget):
logger.debug("Emptying Trash folder")
if trashHandle is None:
self.makeAlert(
- "There is currently no Trash folder in this project.", nwAlert.INFO
+ self.tr("There is currently no Trash folder in this project."), nwAlert.INFO
)
return False
@@ -438,11 +445,12 @@ class GuiProjectTree(QTreeWidget):
nTrash = len(theTrash)
if nTrash == 0:
- self.makeAlert("The Trash folder is already empty.", nwAlert.INFO)
+ self.makeAlert(self.tr("The Trash folder is already empty."), nwAlert.INFO)
return False
msgYes = self.askQuestion(
- "Empty Trash", "Permanently delete %d file(s) from Trash?" % nTrash
+ self.tr("Empty Trash"),
+ self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
)
if not msgYes:
return False
@@ -500,7 +508,8 @@ class GuiProjectTree(QTreeWidget):
doPermanent = False
if not alreadyAsked:
msgYes = self.askQuestion(
- "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
+ self.tr("Delete File"),
+ self.tr("Permanently delete file '{0}'?").format(nwItemS.itemName)
)
if msgYes:
doPermanent = True
@@ -529,7 +538,8 @@ class GuiProjectTree(QTreeWidget):
doTrash = False
if askForTrash:
msgYes = self.askQuestion(
- "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
+ self.tr("Delete File"),
+ self.tr("Move file '{0}' to Trash?").format(nwItemS.itemName),
)
if msgYes:
doTrash = True
@@ -564,9 +574,9 @@ class GuiProjectTree(QTreeWidget):
self._setTreeChanged(True)
else:
self.makeAlert((
- "Cannot delete folder. It is not empty. "
- "Recursive deletion is not supported. "
- "Please delete the content first."
+ self.tr("Cannot delete folder. It is not empty."),
+ self.tr("Recursive deletion is not supported."),
+ self.tr("Please delete the content first."),
), nwAlert.ERROR)
return False
@@ -580,9 +590,9 @@ class GuiProjectTree(QTreeWidget):
self._setTreeChanged(True)
else:
self.makeAlert((
- "Cannot delete root folder. It is not empty. "
- "Recursive deletion is not supported. "
- "Please delete the content first."
+ self.tr("Cannot delete root folder. It is not empty."),
+ self.tr("Recursive deletion is not supported."),
+ self.tr("Please delete the content first."),
), nwAlert.ERROR)
return False
@@ -835,7 +845,7 @@ class GuiProjectTree(QTreeWidget):
snItem = self.theProject.projTree[sHandle]
dnItem = self.theProject.projTree[dHandle]
if dnItem is None:
- self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
+ self.makeAlert(self.tr("The item cannot be moved to that location."), nwAlert.ERROR)
return
pItem = sItem.parent()
@@ -866,7 +876,7 @@ class GuiProjectTree(QTreeWidget):
else:
theEvent.ignore()
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
- self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
+ self.makeAlert(self.tr("The item cannot be moved to that location."), nwAlert.ERROR)
return
@@ -964,7 +974,9 @@ class GuiProjectTree(QTreeWidget):
self.addTopLevelItem(newItem)
else:
self.makeAlert(
- "There is nowhere to add item with name '%s'" % nwItem.itemName, nwAlert.ERROR
+ self.tr("There is nowhere to add item with name '{0}'").format(
+ nwItem.itemName),
+ nwAlert.ERROR
)
del self._treeMap[tHandle]
return None
@@ -1083,43 +1095,43 @@ class GuiProjectTreeMenu(QMenu):
self.theTree = theTree
self.theItem = None
- self.editItem = QAction("Edit Project Item", self)
+ self.editItem = QAction(self.tr("Edit Project Item"), self)
self.editItem.triggered.connect(self._doEditItem)
self.addAction(self.editItem)
- self.openItem = QAction("Open Document", self)
+ self.openItem = QAction(self.tr("Open Document"), self)
self.openItem.triggered.connect(self._doOpenItem)
self.addAction(self.openItem)
- self.viewItem = QAction("View Document", self)
+ self.viewItem = QAction(self.tr("View Document"), self)
self.viewItem.triggered.connect(self._doViewItem)
self.addAction(self.viewItem)
- self.toggleExp = QAction("Toggle Included Flag", self)
+ self.toggleExp = QAction(self.tr("Toggle Included Flag"), self)
self.toggleExp.triggered.connect(self._doToggleExported)
self.addAction(self.toggleExp)
- self.newFile = QAction("New File", self)
+ self.newFile = QAction(self.tr("New File"), self)
self.newFile.triggered.connect(self._doMakeFile)
self.addAction(self.newFile)
- self.newFolder = QAction("New Folder", self)
+ self.newFolder = QAction(self.tr("New Folder"), self)
self.newFolder.triggered.connect(self._doMakeFolder)
self.addAction(self.newFolder)
- self.deleteItem = QAction("Delete Item", self)
+ self.deleteItem = QAction(self.tr("Delete Item"), self)
self.deleteItem.triggered.connect(self._doDeleteItem)
self.addAction(self.deleteItem)
- self.emptyTrash = QAction("Empty Trash", self)
+ self.emptyTrash = QAction(self.tr("Empty Trash"), self)
self.emptyTrash.triggered.connect(self._doEmptyTrash)
self.addAction(self.emptyTrash)
- self.moveUp = QAction("Move Item Up", self)
+ self.moveUp = QAction(self.tr("Move Item Up"), self)
self.moveUp.triggered.connect(self._doMoveUp)
self.addAction(self.moveUp)
- self.moveDown = QAction("Move Item Down", self)
+ self.moveDown = QAction(self.tr("Move Item Down"), self)
self.moveDown.triggered.connect(self._doMoveDown)
self.addAction(self.moveDown)
diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py
index b1d72520..fc854f16 100644
--- a/nw/gui/projwizard.py
+++ b/nw/gui/projwizard.py
@@ -28,7 +28,7 @@ import nw
import logging
import os
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import QCoreApplication, Qt
from PyQt5.QtWidgets import (
QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit,
QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout,
@@ -94,16 +94,19 @@ class ProjWizardIntroPage(QWizardPage):
self.theWizard = theWizard
self.theTheme = theWizard.theTheme
- self.setTitle("Create New Project")
+ self.setTitle(self.tr("Create New Project"))
self.theText = QLabel(
- "Provide at least a working title. The working title should not "
- "be change beyond this point as it is used by the application for "
- "generating file names for for instance backups. The other fields "
- "are optional and can be changed at any time in Project Settings."
+ self.tr("Provide at least a working title. The working title should not "
+ "be change beyond this point as it is used by the application for "
+ "generating file names for for instance backups. The other fields "
+ "are optional and can be changed at any time in Project Settings.")
)
self.theText.setWordWrap(True)
- self.imgCredit = QLabel("Side image by Peter Mitterhofer, CC BY-SA 4.0")
+ self.imgCredit = QLabel(self.tr("Side image by {author:s}, {license:s}").format(
+ author = "Peter Mitterhofer",
+ license = "CC BY-SA 4.0"
+ ))
lblFont = self.imgCredit.font()
lblFont.setPointSizeF(0.6*self.theTheme.fontPointSize)
self.imgCredit.setFont(lblFont)
@@ -117,22 +120,22 @@ class ProjWizardIntroPage(QWizardPage):
self.projName = QLineEdit()
self.projName.setMaxLength(200)
self.projName.setFixedWidth(xW)
- self.projName.setPlaceholderText("Required")
+ self.projName.setPlaceholderText(self.tr("Required"))
self.projTitle = QLineEdit()
self.projTitle.setMaxLength(200)
self.projTitle.setFixedWidth(xW)
- self.projTitle.setPlaceholderText("Optional")
+ self.projTitle.setPlaceholderText(self.tr("Optional"))
self.projAuthors = QPlainTextEdit()
self.projAuthors.setFixedHeight(xH)
self.projAuthors.setFixedWidth(xW)
- self.projAuthors.setPlaceholderText("Optional. One name per line.")
+ self.projAuthors.setPlaceholderText(self.tr("Optional. One name per line."))
self.mainForm = QFormLayout()
- self.mainForm.addRow("Working Title", self.projName)
- self.mainForm.addRow("Novel Title", self.projTitle)
- self.mainForm.addRow("Author(s)", self.projAuthors)
+ self.mainForm.addRow(self.tr("Working Title"), self.projName)
+ self.mainForm.addRow(self.tr("Novel Title"), self.projTitle)
+ self.mainForm.addRow(self.tr("Author(s)"), self.projAuthors)
self.mainForm.setVerticalSpacing(fS)
self.registerField("projName*", self.projName)
@@ -161,10 +164,10 @@ class ProjWizardFolderPage(QWizardPage):
self.theWizard = theWizard
self.theTheme = theWizard.theTheme
- self.setTitle("Select Project Folder")
+ self.setTitle(self.tr("Select Project Folder"))
self.theText = QLabel(
- "Select a location to store the project. A new project folder "
- "will be created in the selected location."
+ self.tr("Select a location to store the project. A new project folder "
+ "will be created in the selected location.")
)
self.theText.setWordWrap(True)
@@ -174,14 +177,14 @@ class ProjWizardFolderPage(QWizardPage):
self.projPath = QLineEdit("")
self.projPath.setFixedWidth(xW)
- self.projPath.setPlaceholderText("Required")
+ self.projPath.setPlaceholderText(self.tr("Required"))
self.browseButton = QPushButton("...")
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
self.browseButton.clicked.connect(self._doBrowse)
self.mainForm = QHBoxLayout()
- self.mainForm.addWidget(QLabel("Project Path"), 0)
+ self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0)
self.mainForm.addWidget(self.projPath, 1)
self.mainForm.addWidget(self.browseButton, 0)
self.mainForm.setSpacing(fS)
@@ -213,7 +216,7 @@ class ProjWizardFolderPage(QWizardPage):
dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog
projDir = QFileDialog.getExistingDirectory(
- self, "Select Project Folder", lastPath, options=dlgOpt
+ self, self.tr("Select Project Folder"), lastPath, options=dlgOpt
)
if projDir:
projName = self.field("projName")
@@ -235,20 +238,20 @@ class ProjWizardPopulatePage(QWizardPage):
self.mainConf = nw.CONFIG
self.theWizard = theWizard
- self.setTitle("Populate Project")
+ self.setTitle(self.tr("Populate Project"))
self.theText = QLabel(
- "Choose how to pre-fill the project. Either with a minimal set of "
- "starter items, an example project explaining and showing many of "
- "the features, or show further custom options on the next page."
+ self.tr("Choose how to pre-fill the project. Either with a minimal set of "
+ "starter items, an example project explaining and showing many of "
+ "the features, or show further custom options on the next page.")
)
self.theText.setWordWrap(True)
vS = self.mainConf.pxInt(12)
fS = self.mainConf.pxInt(4)
- self.popMinimal = QRadioButton("Fill the project with a minimal set of items")
- self.popSample = QRadioButton("Fill the project with example files")
- self.popCustom = QRadioButton("Show detailed options for filling the project")
+ self.popMinimal = QRadioButton(self.tr("Fill the project with a minimal set of items"))
+ self.popSample = QRadioButton(self.tr("Fill the project with example files"))
+ self.popCustom = QRadioButton(self.tr("Show detailed options for filling the project"))
self.popMinimal.setChecked(True)
self.popBox = QVBoxLayout()
@@ -290,27 +293,33 @@ class ProjWizardCustomPage(QWizardPage):
self.mainConf = nw.CONFIG
self.theWizard = theWizard
- self.setTitle("Custom Project Options")
+ self.setTitle(self.tr("Custom Project Options"))
self.theText = QLabel(
- "Select which additional root folders to make, and how to populate "
- "the Novel folder. If you don't want to add chapters or scenes, set "
- "the values to 0. You can add scenes without chapters."
+ self.tr("Select which additional root folders to make, and how to populate "
+ "the Novel folder. If you don't want to add chapters or scenes, set "
+ "the values to 0. You can add scenes without chapters.")
)
self.theText.setWordWrap(True)
vS = self.mainConf.pxInt(12)
# Root Folders
- self.rootGroup = QGroupBox("Additional Root Folders")
+ self.rootGroup = QGroupBox(self.tr("Additional Root Folders"))
self.rootForm = QGridLayout()
self.rootGroup.setLayout(self.rootForm)
- self.lblPlot = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.PLOT])
- self.lblChar = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.CHARACTER])
- self.lblWorld = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.WORLD])
- self.lblTime = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.TIMELINE])
- self.lblObject = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.OBJECT])
- self.lblEntity = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.ENTITY])
+ self.lblPlot = QLabel(self.tr("{0} folder").format(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.PLOT])))
+ self.lblChar = QLabel(self.tr("{0} folder").format(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.CHARACTER])))
+ self.lblWorld = QLabel(self.tr("{0} folder").format(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.WORLD])))
+ self.lblTime = QLabel(self.tr("{0} folder").format(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.TIMELINE])))
+ self.lblObject = QLabel(self.tr("{0} folder").format(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.OBJECT])))
+ self.lblEntity = QLabel(self.tr("{0} folder").format(
+ QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.ENTITY])))
self.addPlot = QSwitch()
self.addChar = QSwitch()
@@ -338,7 +347,7 @@ class ProjWizardCustomPage(QWizardPage):
self.rootForm.setRowStretch(6, 1)
# Novel Options
- self.novelGroup = QGroupBox("Populate Novel Folder")
+ self.novelGroup = QGroupBox(self.tr("Populate Novel Folder"))
self.novelForm = QGridLayout()
self.novelGroup.setLayout(self.novelForm)
@@ -353,9 +362,9 @@ class ProjWizardCustomPage(QWizardPage):
self.chFolders = QSwitch()
self.chFolders.setChecked(True)
- self.novelForm.addWidget(QLabel("Add chapters"), 0, 0)
- self.novelForm.addWidget(QLabel("Scenes (per chapter)"), 1, 0)
- self.novelForm.addWidget(QLabel("Add chapter folders"), 2, 0)
+ self.novelForm.addWidget(QLabel(self.tr("Add chapters")), 0, 0)
+ self.novelForm.addWidget(QLabel(self.tr("Scenes (per chapter)")), 1, 0)
+ self.novelForm.addWidget(QLabel(self.tr("Add chapter folders")), 2, 0)
self.novelForm.addWidget(self.numChapters, 0, 1, 1, 1, Qt.AlignRight)
self.novelForm.addWidget(self.numScenes, 1, 1, 1, 1, Qt.AlignRight)
self.novelForm.addWidget(self.chFolders, 2, 1, 1, 1, Qt.AlignRight)
@@ -396,13 +405,12 @@ class ProjWizardFinalPage(QWizardPage):
self.mainConf = nw.CONFIG
self.theWizard = theWizard
- self.setTitle("Finished")
- self.theText = QLabel((
- "All done.
"
- "Press '{finish}' to create the new project.
"
- ).format(
- finish = "Done" if self.mainConf.osDarwin else "Finish"
- ))
+ self.setTitle(self.tr("Finished"))
+ self.theText = QLabel("".join([
+ ("%s
" % self.tr("All done.")),
+ ("%s
" % self.tr("Press '{0}' to create the new project.").format(
+ self.tr("Done") if self.mainConf.osDarwin else self.tr("Finish")))
+ ]))
self.theText.setWordWrap(True)
# Assemble
diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py
index 441cacda..617bbbbe 100644
--- a/nw/gui/statusbar.py
+++ b/nw/gui/statusbar.py
@@ -63,7 +63,7 @@ class GuiMainStatus(QStatusBar):
## The Spell Checker Language
self.langIcon = QLabel("")
- self.langText = QLabel("None")
+ self.langText = QLabel(self.tr("None"))
self.langIcon.setPixmap(self.theTheme.getPixmap("status_lang", (iPx, iPx)))
self.langIcon.setContentsMargins(0, 0, 0, 0)
self.langText.setContentsMargins(0, 0, xM, 0)
@@ -72,7 +72,7 @@ class GuiMainStatus(QStatusBar):
## The Editor Status
self.docIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
- self.docText = QLabel("Editor")
+ self.docText = QLabel(self.tr("Editor"))
self.docIcon.setContentsMargins(0, 0, 0, 0)
self.docText.setContentsMargins(0, 0, xM, 0)
self.addPermanentWidget(self.docIcon)
@@ -80,7 +80,7 @@ class GuiMainStatus(QStatusBar):
## The Project Status
self.projIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
- self.projText = QLabel("Project")
+ self.projText = QLabel(self.tr("Project"))
self.projIcon.setContentsMargins(0, 0, 0, 0)
self.projText.setContentsMargins(0, 0, xM, 0)
self.addPermanentWidget(self.projIcon)
@@ -103,7 +103,7 @@ class GuiMainStatus(QStatusBar):
self.timeIcon = QLabel()
self.timeText = QLabel("")
self.timeIcon.setPixmap(self.timePixmap)
- self.timeText.setToolTip("Session Time")
+ self.timeText.setToolTip(self.tr("Session Time"))
self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:"))
self.timeIcon.setContentsMargins(0, 0, 0, 0)
self.timeText.setContentsMargins(0, 0, 0, 0)
@@ -151,12 +151,14 @@ class GuiMainStatus(QStatusBar):
"""Set the language code for the spell checker.
"""
if theLanguage is None:
- self.langText.setText("None")
+ self.langText.setText(self.tr("None"))
self.langText.setToolTip("")
else:
self.langText.setText(NWSpellCheck.expandLanguage(theLanguage))
self.langText.setToolTip(
- "Provider: %s" % (theProvider if theProvider else "unknown")
+ self.tr("{0}: {1}").format(
+ self.tr("Provider"),
+ theProvider if theProvider else self.tr("unknown"))
)
return
@@ -175,8 +177,8 @@ class GuiMainStatus(QStatusBar):
def setStats(self, pWC, sWC):
"""Set the current project statistics.
"""
- self.statsText.setText(f"Words: {pWC:n} ({sWC:+n})")
- self.statsText.setToolTip("Project word count (session change)")
+ self.statsText.setText("%s: %s (%s)" % (self.tr("Words"), f"{pWC:n}", f"{sWC:+n}"))
+ self.statsText.setToolTip(self.tr("Project word count (session change)"))
return
def setUserIdle(self, userIdle):
diff --git a/nw/gui/theme.py b/nw/gui/theme.py
index 9ec6018e..fc30a839 100644
--- a/nw/gui/theme.py
+++ b/nw/gui/theme.py
@@ -393,7 +393,7 @@ class GuiTheme:
confParser.read_file(inFile)
except Exception as e:
self.theParent.makeAlert(
- ["Could not load theme config file.", str(e)], nwAlert.ERROR
+ [self.tr("Could not load theme config file."), str(e)], nwAlert.ERROR
)
continue
themeName = ""
@@ -426,7 +426,7 @@ class GuiTheme:
confParser.read_file(inFile)
except Exception as e:
self.theParent.makeAlert(
- ["Could not load syntax file.", str(e)], nwAlert.ERROR
+ [self.tr("Could not load syntax file."), str(e)], nwAlert.ERROR
)
return []
syntaxName = ""
@@ -741,7 +741,7 @@ class GuiIcons:
confParser.read_file(inFile)
except Exception as e:
self.theParent.makeAlert(
- ["Could not load theme config file.", str(e)], nwAlert.ERROR
+ [self.tr("Could not load theme config file."), str(e)], nwAlert.ERROR
)
continue
themeName = ""
diff --git a/nw/gui/wordlist.py b/nw/gui/wordlist.py
index f5b6fe80..0e7f3747 100644
--- a/nw/gui/wordlist.py
+++ b/nw/gui/wordlist.py
@@ -52,7 +52,7 @@ class GuiWordList(QDialog):
self.theProject = theProject
self.optState = theProject.optState
- self.setWindowTitle("Project Word List")
+ self.setWindowTitle(self.tr("Project Word List"))
mS = self.mainConf.pxInt(250)
wW = self.mainConf.pxInt(320)
@@ -68,7 +68,7 @@ class GuiWordList(QDialog):
# Main Widgets
# ============
- self.headLabel = QLabel("Project Word List")
+ self.headLabel = QLabel("%s" % self.tr("Project Word List"))
self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
@@ -77,11 +77,11 @@ class GuiWordList(QDialog):
self.newEntry = QLineEdit()
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
- self.addButton.setToolTip("Add new entry")
+ self.addButton.setToolTip(self.tr("Add new entry"))
self.addButton.clicked.connect(self._doAdd)
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
- self.delButton.setToolTip("Delete selected entry")
+ self.delButton.setToolTip(self.tr("Delete selected entry"))
self.delButton.clicked.connect(self._doDelete)
self.editBox = QHBoxLayout()
@@ -90,6 +90,8 @@ class GuiWordList(QDialog):
self.editBox.addWidget(self.delButton, 0)
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
+ self.buttonBox.button(QDialogButtonBox.Save).setText(self.tr("Save"))
+ self.buttonBox.button(QDialogButtonBox.Close).setText(self.tr("Close"))
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
@@ -121,12 +123,13 @@ class GuiWordList(QDialog):
"""
newWord = self.newEntry.text().strip()
if newWord == "":
- self.theParent.makeAlert("Cannot add a blank word.", nwAlert.ERROR)
+ self.theParent.makeAlert(self.tr("Cannot add a blank word."), nwAlert.ERROR)
return False
if self.listBox.findItems(newWord, Qt.MatchExactly):
self.theParent.makeAlert(
- "The word '%s' is already in the word list." % newWord, nwAlert.ERROR
+ self.tr("The word '{0}' is already in the word list.").format(newWord),
+ nwAlert.ERROR
)
return False
diff --git a/nw/gui/writingstats.py b/nw/gui/writingstats.py
index dec1fef4..44fd38e5 100644
--- a/nw/gui/writingstats.py
+++ b/nw/gui/writingstats.py
@@ -72,7 +72,7 @@ class GuiWritingStats(QDialog):
self.timeFilter = 0.0
self.wordOffset = 0
- self.setWindowTitle("Writing Statistics")
+ self.setWindowTitle(self.tr("Writing Statistics"))
self.setMinimumWidth(self.mainConf.pxInt(420))
self.setMinimumHeight(self.mainConf.pxInt(400))
self.resize(
@@ -95,7 +95,13 @@ class GuiWritingStats(QDialog):
)
self.listBox = QTreeWidget()
- self.listBox.setHeaderLabels(["Session Start", "Length", "Idle", "Words", "Histogram"])
+ self.listBox.setHeaderLabels([
+ self.tr("Session Start"),
+ self.tr("Length"),
+ self.tr("Idle"),
+ self.tr("Words"),
+ self.tr("Histogram"),
+ ])
self.listBox.setIndentation(0)
self.listBox.setColumnWidth(self.C_TIME, wCol0)
self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
@@ -125,7 +131,7 @@ class GuiWritingStats(QDialog):
self.barImage.fill(self.palette().highlight().color())
# Session Info
- self.infoBox = QGroupBox("Sum Totals", self)
+ self.infoBox = QGroupBox(self.tr("Sum Totals"), self)
self.infoForm = QGridLayout(self)
self.infoBox.setLayout(self.infoForm)
@@ -153,12 +159,12 @@ class GuiWritingStats(QDialog):
self.totalWords.setFont(self.theTheme.guiFontFixed)
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
- self.infoForm.addWidget(QLabel("Total Time:"), 0, 0)
- self.infoForm.addWidget(QLabel("Idle Time:"), 1, 0)
- self.infoForm.addWidget(QLabel("Filtered Time:"), 2, 0)
- self.infoForm.addWidget(QLabel("Novel Word Count:"), 3, 0)
- self.infoForm.addWidget(QLabel("Notes Word Count:"), 4, 0)
- self.infoForm.addWidget(QLabel("Total Word Count:"), 5, 0)
+ self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Total Time"))), 0, 0)
+ self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Idle Time"))), 1, 0)
+ self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Filtered Time"))), 2, 0)
+ self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Novel Word Count"))), 3, 0)
+ self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Notes Word Count"))), 4, 0)
+ self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Total Word Count"))), 5, 0)
self.infoForm.addWidget(self.labelTotal, 0, 1)
self.infoForm.addWidget(self.labelIdleT, 1, 1)
self.infoForm.addWidget(self.labelFilter, 2, 1)
@@ -170,7 +176,7 @@ class GuiWritingStats(QDialog):
# Filter Options
sPx = self.theTheme.baseIconSize
- self.filterBox = QGroupBox("Filters", self)
+ self.filterBox = QGroupBox(self.tr("Filters"), self)
self.filterForm = QGridLayout(self)
self.filterBox.setLayout(self.filterForm)
@@ -210,12 +216,12 @@ class GuiWritingStats(QDialog):
)
self.showIdleTime.clicked.connect(self._updateListBox)
- self.filterForm.addWidget(QLabel("Count novel files"), 0, 0)
- self.filterForm.addWidget(QLabel("Count note files"), 1, 0)
- self.filterForm.addWidget(QLabel("Hide zero word count"), 2, 0)
- self.filterForm.addWidget(QLabel("Hide negative word count"), 3, 0)
- self.filterForm.addWidget(QLabel("Group entries by day"), 4, 0)
- self.filterForm.addWidget(QLabel("Show idle time"), 5, 0)
+ self.filterForm.addWidget(QLabel(self.tr("Count novel files")), 0, 0)
+ self.filterForm.addWidget(QLabel(self.tr("Count note files")), 1, 0)
+ self.filterForm.addWidget(QLabel(self.tr("Hide zero word count")), 2, 0)
+ self.filterForm.addWidget(QLabel(self.tr("Hide negative word count")), 3, 0)
+ self.filterForm.addWidget(QLabel(self.tr("Group entries by day")), 4, 0)
+ self.filterForm.addWidget(QLabel(self.tr("Show idle time")), 5, 0)
self.filterForm.addWidget(self.incNovel, 0, 1)
self.filterForm.addWidget(self.incNotes, 1, 1)
self.filterForm.addWidget(self.hideZeros, 2, 1)
@@ -236,7 +242,7 @@ class GuiWritingStats(QDialog):
self.optsBox = QHBoxLayout()
self.optsBox.addStretch(1)
- self.optsBox.addWidget(QLabel("Word count cap for the histogram"), 0)
+ self.optsBox.addWidget(QLabel(self.tr("Word count cap for the histogram")), 0)
self.optsBox.addWidget(self.histMax, 0)
# Buttons
@@ -244,19 +250,22 @@ class GuiWritingStats(QDialog):
self.buttonBox.rejected.connect(self._doClose)
self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
+ self.buttonBox.button(QDialogButtonBox.Close).setText(self.tr("Close"))
self.btnClose.setAutoDefault(False)
- self.btnSave = self.buttonBox.addButton("Save As", QDialogButtonBox.ActionRole)
+ self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QDialogButtonBox.ActionRole)
self.btnSave.setAutoDefault(False)
self.saveMenu = QMenu(self)
self.btnSave.setMenu(self.saveMenu)
- self.saveJSON = QAction("JSON Data File (.json)", self)
+ self.saveJSON = QAction(self.tr("{0} ({1})").format(
+ self.tr("JSON Data File"), ".json"), self)
self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON))
self.saveMenu.addAction(self.saveJSON)
- self.saveCSV = QAction("CSV Data File (.csv)", self)
+ self.saveCSV = QAction(self.tr("{0} ({1})").format(
+ self.tr("CSV Data File"), ".csv"), self)
self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV))
self.saveMenu.addAction(self.saveCSV)
@@ -338,10 +347,10 @@ class GuiWritingStats(QDialog):
if dataFmt == self.FMT_JSON:
fileExt = "json"
- textFmt = "JSON Data File"
+ textFmt = self.tr("JSON Data File")
elif dataFmt == self.FMT_CSV:
fileExt = "csv"
- textFmt = "CSV Data File"
+ textFmt = self.tr("CSV Data File")
else:
return False
@@ -356,7 +365,7 @@ class GuiWritingStats(QDialog):
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
savePath, _ = QFileDialog.getSaveFileName(
- self, "Save Document As", savePath, options=dlgOpt
+ self, self.tr("Save Document As"), savePath, options=dlgOpt
)
if not savePath:
return False
@@ -474,7 +483,7 @@ class GuiWritingStats(QDialog):
except Exception as e:
self.theParent.makeAlert(
- ["Failed to read session log file.", str(e)], nwAlert.ERROR
+ [self.tr("Failed to read session log file."), str(e)], nwAlert.ERROR
)
return False
diff --git a/nw/guimain.py b/nw/guimain.py
index 6cb68cb3..50123955 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -122,8 +122,8 @@ class GuiMain(QMainWindow):
self.projTabs = QTabWidget()
self.projTabs.setTabPosition(QTabWidget.South)
self.projTabs.setStyleSheet(r"QTabWidget::pane {border: 0;};")
- self.projTabs.addTab(self.treeView, "Project")
- self.projTabs.addTab(self.novelView, "Novel")
+ self.projTabs.addTab(self.treeView, self.tr("Project"))
+ self.projTabs.addTab(self.novelView, self.tr("Novel"))
self.projTabs.currentChanged.connect(self._projTabsChanged)
tabFont = self.projTabs.tabBar().font()
@@ -139,17 +139,17 @@ class GuiMain(QMainWindow):
self.treeButtons.setStyleSheet(r"QToolBar {padding: 0;}")
self.projTabs.setCornerWidget(self.treeButtons, Qt.BottomRightCorner)
- self.projDetailsBtn = QAction("Project Details")
+ self.projDetailsBtn = QAction(self.tr("Project Details"))
self.projDetailsBtn.setIcon(self.theTheme.getIcon("status_lines"))
self.projDetailsBtn.triggered.connect(lambda: self.showProjectDetailsDialog())
self.treeButtons.addAction(self.projDetailsBtn)
- self.projStatsBtn = QAction("Writing Statistics")
+ self.projStatsBtn = QAction(self.tr("Writing Statistics"))
self.projStatsBtn.setIcon(self.theTheme.getIcon("status_stats"))
self.projStatsBtn.triggered.connect(lambda: self.showWritingStatsDialog())
self.treeButtons.addAction(self.projStatsBtn)
- self.projSettingsBtn = QAction("Project Settings")
+ self.projSettingsBtn = QAction(self.tr("Project Settings"))
self.projSettingsBtn.setIcon(self.theTheme.getIcon("settings"))
self.projSettingsBtn.triggered.connect(lambda: self.showProjectSettingsDialog())
self.treeButtons.addAction(self.projSettingsBtn)
@@ -180,12 +180,12 @@ class GuiMain(QMainWindow):
self.splitOutline.addWidget(self.projMeta)
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
- # Main Tabs : Editor / Outline
+ # Main Tabs : Edirot / Outline
self.mainTabs = QTabWidget()
self.mainTabs.setTabPosition(QTabWidget.East)
self.mainTabs.setStyleSheet(r"QTabWidget::pane {border: 0;}")
- self.mainTabs.addTab(self.splitDocs, "Editor")
- self.mainTabs.addTab(self.splitOutline, "Outline")
+ self.mainTabs.addTab(self.splitDocs, self.tr("Editor"))
+ self.mainTabs.addTab(self.splitOutline, self.tr("Outline"))
self.mainTabs.currentChanged.connect(self._mainTabChanged)
# Splitter : Project Tree / Main Tabs
@@ -339,7 +339,7 @@ class GuiMain(QMainWindow):
if self.hasProject:
if not self.closeProject():
self.makeAlert(
- "Cannot create new project when another project is open.",
+ self.tr("Cannot create new project when another project is open."),
nwAlert.ERROR
)
return False
@@ -357,7 +357,8 @@ class GuiMain(QMainWindow):
if os.path.isfile(os.path.join(projPath, self.theProject.projFile)):
self.makeAlert(
- "A project already exists in that location. Please choose another folder.",
+ self.tr("A project already exists in that location. "
+ "Please choose another folder."),
nwAlert.ERROR
)
return False
@@ -372,7 +373,7 @@ class GuiMain(QMainWindow):
self.statusBar.setRefTime(self.theProject.projOpened)
self.statusBar.setProjectStatus(True)
self.statusBar.setDocumentStatus(None)
- self.statusBar.setStatus("New project created ...")
+ self.statusBar.setStatus(self.tr("New project created ..."))
self._updateWindowTitle(self.theProject.projName)
else:
self.theProject.clearProject()
@@ -391,8 +392,9 @@ class GuiMain(QMainWindow):
if not isYes:
msgYes = self.askQuestion(
- "Close Project",
- "Close the current project?
Changes are saved automatically."
+ self.tr("Close Project"),
+ "%s
%s" % (self.tr("Close the current project?"),
+ self.tr("Changes are saved automatically."))
)
if not msgYes:
return False
@@ -407,7 +409,8 @@ class GuiMain(QMainWindow):
doBackup = True
if self.mainConf.askBeforeBackup:
msgYes = self.askQuestion(
- "Backup Project", "Backup the current project?"
+ self.tr("Backup Project"),
+ self.tr("Backup the current project?")
)
if not msgYes:
doBackup = False
@@ -457,13 +460,14 @@ class GuiMain(QMainWindow):
try:
lockDetails = (
- "
The project was locked by the computer "
- "'%s' (%s %s), last active on %s"
- ) % (
- self.theProject.lockedBy[0],
- self.theProject.lockedBy[1],
- self.theProject.lockedBy[2],
- datetime.fromtimestamp(
+ "
%s" % self.tr("The project was locked by the computer "
+ "'{computer_name}' ({os_name} {os_version}), "
+ "last active on {time}")
+ ).format(
+ computer_name = self.theProject.lockedBy[0],
+ os_name = self.theProject.lockedBy[1],
+ os_version = self.theProject.lockedBy[2],
+ time = datetime.fromtimestamp(
int(self.theProject.lockedBy[3])
).strftime("%x %X")
)
@@ -472,14 +476,16 @@ class GuiMain(QMainWindow):
msgBox = QMessageBox()
msgRes = msgBox.warning(
- self, "Project Locked", (
- "The project is already open by another instance of novelWriter, and "
- "is therefore locked. Override lock and continue anyway?
"
- "Note: If the program or the computer previously crashed, the lock "
- "can safely be overridden. If, however, another instance of "
- "novelWriter has the project open, overriding the lock may corrupt "
- "the project, and is not recommended.%s"
- ) % lockDetails,
+ self, self.tr("Project Locked"),
+ "%s
%s
%s" % (
+ self.tr("The project is already open by another instance of novelWriter, and "
+ "is therefore locked. Override lock and continue anyway?"),
+ self.tr("Note: If the program or the computer previously crashed, the lock "
+ "can safely be overridden. If, however, another instance of "
+ "novelWriter has the project open, overriding the lock may corrupt "
+ "the project, and is not recommended."),
+ lockDetails
+ ),
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if msgRes == QMessageBox.Yes:
@@ -685,15 +691,16 @@ class GuiMain(QMainWindow):
lastPath = self.mainConf.lastPath
extFilter = [
- "Text files (*.txt)",
- "Markdown files (*.md)",
- "novelWriter files (*.nwd)",
- "All files (*.*)",
+ self.tr("{0} ({1})").format(self.tr("Text files"), "*.txt"),
+ self.tr("{0} ({1})").format(self.tr("Markdown files")),
+ self.tr("{0} ({1})").format(self.tr("novelWriter files"), "*.nwd"),
+ self.tr("{0} ({1})").format(self.tr("All files")),
]
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
loadFile, _ = QFileDialog.getOpenFileName(
- self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter)
+ self, self.tr("Import File"), lastPath,
+ options=dlgOpt, filter=";;".join(extFilter)
)
if not loadFile:
return False
@@ -708,22 +715,25 @@ class GuiMain(QMainWindow):
self.mainConf.setLastPath(loadFile)
except Exception as e:
self.makeAlert(
- ["Could not read file. The file must be an existing text file.", str(e)],
+ [
+ self.tr("Could not read file. The file must be an existing text file."),
+ str(e)
+ ],
nwAlert.ERROR
)
return False
if self.docEditor.theHandle is None:
self.makeAlert(
- "Please open a document to import the text file into.",
+ self.tr("Please open a document to import the text file into."),
nwAlert.ERROR
)
return False
if not self.docEditor.isEmpty():
- msgYes = self.askQuestion("Import Document", (
- "Importing the file will overwrite the current content of the document. "
- "Do you want to proceed?"
+ msgYes = self.askQuestion(self.tr("Import Document"), (
+ self.tr("Importing the file will overwrite the current content of the document. "
+ "Do you want to proceed?")
))
if not msgYes:
return False
@@ -861,9 +871,11 @@ class GuiMain(QMainWindow):
for nDone, tItem in enumerate(self.theProject.projTree):
if tItem is not None:
- self.setStatus("Indexing: '%s'" % tItem.itemName)
+ self.setStatus(self.tr("{0}: '{1}'").format(self.tr("Indexing"), tItem.itemName))
else:
- self.setStatus("Indexing: Unknown item")
+ self.setStatus(self.tr("{0}: {1}").format(
+ self.tr("Indexing"),
+ self.tr("Unknown item")))
if tItem is not None and tItem.itemType == nwItemType.FILE:
logger.verbose("Scanning: %s" % tItem.itemName)
@@ -881,12 +893,14 @@ class GuiMain(QMainWindow):
self.treeView.projectWordCount()
tEnd = time()
- self.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
+ self.setStatus(self.tr("Indexing completed in {0} ms").
+ format(f"{(tEnd - tStart)*1000.0:.1f}"))
self.docEditor.updateTagHighLighting()
qApp.restoreOverrideCursor()
if not beQuiet:
- self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO)
+ self.makeAlert(self.tr("The project index has been successfully rebuilt."),
+ nwAlert.INFO)
return True
@@ -914,7 +928,7 @@ class GuiMain(QMainWindow):
dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog
projPath = QFileDialog.getExistingDirectory(
- self, "Save novelWriter Project", "", options=dlgOpt
+ self, self.tr("Save novelWriter Project"), "", options=dlgOpt
)
if projPath:
return projPath
@@ -1100,14 +1114,14 @@ class GuiMain(QMainWindow):
# Popup
msgBox = QMessageBox()
if theLevel == nwAlert.INFO:
- msgBox.information(self, "Information", popMsg)
+ msgBox.information(self, self.tr("Information"), popMsg)
elif theLevel == nwAlert.WARN:
- msgBox.warning(self, "Warning", popMsg)
+ msgBox.warning(self, self.tr("Warning"), popMsg)
elif theLevel == nwAlert.ERROR:
- msgBox.critical(self, "Error", popMsg)
+ msgBox.critical(self, self.tr("Error"), popMsg)
elif theLevel == nwAlert.BUG:
- popMsg += "
This is a bug!"
- msgBox.critical(self, "Internal Error", popMsg)
+ popMsg += "
%s" % self.tr("This is a bug!")
+ msgBox.critical(self, self.tr("Internal Error"), popMsg)
return
@@ -1115,7 +1129,8 @@ class GuiMain(QMainWindow):
"""Ask the user a Yes/No question.
"""
msgBox = QMessageBox()
- msgRes = msgBox.question(self, theTitle, theQuestion)
+ print(msgBox.standardButtons())
+ msgRes = msgBox.question(self, theTitle, theQuestion, QMessageBox.Yes | QMessageBox.No)
return msgRes == QMessageBox.Yes
def reportConfErr(self):
@@ -1137,8 +1152,9 @@ class GuiMain(QMainWindow):
"""
if self.hasProject:
msgYes = self.askQuestion(
- "Exit",
- "Do you want to exit novelWriter?
Changes are saved automatically."
+ self.tr("Exit"),
+ "%s
%s" % (self.tr("Do you want to exit novelWriter?"),
+ self.tr("Changes are saved automatically."))
)
if not msgYes:
return False
diff --git a/nw/languages/nw_pt.qm b/nw/languages/nw_pt.qm
new file mode 100644
index 0000000000000000000000000000000000000000..a6d88a4967836e32a7c948c4d2aeec9d0461db59
GIT binary patch
literal 145009
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%0Dh7-
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