"""
novelWriter – Assets
====================
This file is a part of novelWriter
Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
""" # noqa
from __future__ import annotations
import argparse
import subprocess
import sys
import zipfile
from pathlib import Path
from utils.common import ROOT_DIR, writeFile
from utils.docs import buildPdfDocAssets
def buildSampleZip(args: argparse.Namespace | None = None) -> None:
"""Bundle the sample project into a single zip file to be saved into
the novelwriter/assets folder for further bundling into builds.
"""
print("")
print("Building Sample ZIP File")
print("========================")
print("")
srcSample = ROOT_DIR / "sample"
dstSample = ROOT_DIR / "novelwriter" / "assets" / "sample.zip"
if srcSample.is_dir():
dstSample.unlink(missing_ok=True)
with zipfile.ZipFile(
dstSample, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=3
) as zipObj:
print("Compressing: nwProject.nwx")
zipObj.write(srcSample / "nwProject.nwx", "nwProject.nwx")
for doc in (srcSample / "content").iterdir():
print(f"Compressing: content/{doc.name}")
zipObj.write(doc, f"content/{doc.name}")
else:
print("Error: Could not find sample project source directory.")
sys.exit(1)
print("")
print(f"Built file: {dstSample}")
print("")
def importI18nUpdates(args: argparse.Namespace) -> None:
"""Import new translation files from a zip file."""
print("")
print("Import Updated Translations")
print("===========================")
print("")
fileName = Path(args.file).absolute()
if not fileName.is_file():
print("File not found ...")
sys.exit(1)
dstPath = ROOT_DIR / "novelwriter" / "assets" / "i18n"
srcPath = ROOT_DIR / "i18n"
print(f"Loading file: {fileName}")
with zipfile.ZipFile(fileName) as zipObj:
for item in zipObj.namelist():
if item.startswith("nw_") and item.endswith(".ts"):
zipObj.extract(item, srcPath)
print(f"Extracted: {item} > {srcPath / item}")
elif item.startswith("project_") and item.endswith(".json"):
zipObj.extract(item, dstPath)
print(f"Extracted: {item} > {dstPath / item}")
else:
print(f"Skipped: {item}")
print("")
def updateTranslationSources(args: argparse.Namespace) -> None:
"""Build the lang.ts files for Qt Linguist."""
print("")
print("Building Qt Translation Files")
print("=============================")
try:
from PyQt6.lupdate.lupdate import lupdate
except ImportError:
print("ERROR: This command requires lupdate from PyQt6")
print("On Debian/Ubuntu, install: pyqt6-dev-tools")
sys.exit(1)
print("")
print("Scanning Source Tree:")
print("")
sources = list((ROOT_DIR / "novelwriter").glob("**/*.py"))
for source in sources:
print(source.relative_to(ROOT_DIR))
print("")
print("TS Files to Update:")
print("")
translations = []
for item in [Path(str(f)).absolute() for f in args.files]:
if not (item.name.startswith("nw_") and item.suffix == ".ts"):
print(f"Skipped: {item}")
continue
if item.is_file():
translations.append(item)
print(f"Added: {item}")
elif item.exists():
continue
else: # Create an empty new language file
langCode = item.name[3:-3]
writeFile(item, (
'\n'
"\n"
f'\n'
))
translations.append(item)
print(f"Created: {item}")
print("")
print("Updating Language Files:")
print("")
lupdate(
sources=[str(f) for f in sources],
translation_files=[str(f) for f in translations],
no_obsolete=True,
no_summary=False,
)
print("")
def buildTranslationAssets(args: argparse.Namespace | None = None) -> None:
"""Build the lang.qm files for Qt Linguist."""
print("")
print("Building Qt Localisation Files")
print("==============================")
print("")
print("TS Files to Build:")
print("")
srcDir = ROOT_DIR / "i18n"
dstDir = ROOT_DIR / "novelwriter" / "assets" / "i18n"
srcList = []
for item in srcDir.iterdir():
if item.is_file() and item.suffix == ".ts" and item.name != "nw_base.ts":
srcList.append(item)
print(item)
print("")
print("Building Translation Files:")
print("")
try:
subprocess.call(["lrelease", "-verbose", *srcList])
except Exception as exc:
print("Qt Linguist tools seem to be missing")
print("On Debian/Ubuntu, install: qttools5-dev-tools")
print(str(exc))
sys.exit(1)
print("")
print("Moving QM Files to Assets")
print("")
dstRel = dstDir.relative_to(ROOT_DIR)
for item in srcDir.iterdir():
if item.is_file() and item.suffix == ".qm":
item.rename(dstDir / item.name)
print(f"Moved: {item.relative_to(ROOT_DIR)} -> {dstRel / item.name}")
print("")
def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None:
"""Remove assets built by this script."""
print("")
print("Removing Built Assets")
print("=====================")
print("")
assets = [ROOT_DIR / "novelwriter" / "assets" / "sample.zip"]
assets.extend((ROOT_DIR / "novelwriter" / "assets").glob("manual*.pdf"))
assets.extend((ROOT_DIR / "novelwriter" / "assets" / "i18n").glob("*.qm"))
for asset in assets:
if asset.is_file():
asset.unlink()
print(f"Deleted: {asset.relative_to(ROOT_DIR)}")
print("")
def buildAllAssets(args: argparse.Namespace) -> None:
"""Build all assets."""
cleanBuiltAssets()
buildSampleZip()
buildTranslationAssets()
buildPdfDocAssets()