Merge branch 'main' into tags

This commit is contained in:
Miguel Jacq 2025-11-14 12:12:38 +11:00
commit df7ae0b42d
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
4 changed files with 226 additions and 10 deletions

View file

@ -3,6 +3,8 @@
* Fix a few small matters identified with tests * Fix a few small matters identified with tests
* Make locales dynamically detected from the locales dir rather than hardcoded * Make locales dynamically detected from the locales dir rather than hardcoded
* Add version information in the navigation * Add version information in the navigation
* Increase line spacing between lines (except for code blocks)
* Add Italian translations (thanks @mdaleo404)
# 0.2.1.8 # 0.2.1.8

114
bouquin/locales/it.json Normal file
View file

@ -0,0 +1,114 @@
{
"db_sqlcipher_integrity_check_failed": "Controllo di integrità SQLCipher fallito",
"db_issues_reported": "problema/i segnalato/i",
"db_reopen_failed_after_rekey": "Riapertura fallita dopo il cambio chiave",
"db_version_id_does_not_belong_to_the_given_date": "version_id non appartiene alla data indicata",
"db_key_incorrect": "La chiave è probabilmente errata",
"db_database_error": "Errore del database",
"database_path": "Percorso del database",
"database_maintenance": "Manutenzione del database",
"database_compact": "Compatta il database",
"database_compact_explanation": "La compattazione esegue VACUUM sul database. Può aiutare a ridurne le dimensioni.",
"database_compacted_successfully": "Database compattato con successo!",
"encryption": "Crittografia",
"remember_key": "Ricorda la chiave",
"change_encryption_key": "Cambia chiave di crittografia",
"enter_a_new_encryption_key": "Inserisci una nuova chiave di crittografia",
"reenter_the_new_key": "Reinserisci la nuova chiave",
"key_mismatch": "Le chiavi non corrispondono",
"key_mismatch_explanation": "Le due chiavi inserite non corrispondono.",
"empty_key": "Chiave vuota",
"empty_key_explanation": "La chiave non può essere vuota.",
"key_changed": "Chiave cambiata",
"key_changed_explanation": "Il blocco note è stato criptato nuovamente con la nuova chiave!",
"error": "Errore",
"success": "Successo",
"close": "Chiudi",
"find": "Trova",
"file": "File",
"locale": "Lingua",
"locale_restart": "Per favore riavvia l'applicazione per caricare la nuova lingua.",
"settings": "Impostazioni",
"theme": "Tema",
"system": "Sistema",
"light": "Chiaro",
"dark": "Scuro",
"behaviour": "Comportamento",
"never": "Mai",
"browse": "Sfoglia",
"previous": "Precedente",
"previous_day": "Giorno precedente",
"next": "Successivo",
"next_day": "Giorno successivo",
"today": "Oggi",
"show": "Mostra",
"history": "Cronologia",
"view_history": "Visualizza cronologia",
"export": "Esporta",
"export_accessible_flag": "&Esporta",
"export_entries": "Esporta voci",
"export_complete": "Esportazione completata",
"export_failed": "Esportazione fallita",
"backup": "Backup",
"backup_complete": "Backup completato",
"backup_failed": "Backup fallito",
"quit": "Esci",
"help": "Aiuto",
"saved": "Salvato",
"saved_to": "Salvato in",
"documentation": "Documentazione",
"couldnt_open": "Impossibile aprire",
"report_a_bug": "Segnala un bug",
"version": "Versione",
"navigate": "Naviga",
"current": "corrente",
"selected": "selezionato",
"find_on_page": "Trova nella pagina",
"find_next": "Trova successivo",
"find_previous": "Trova precedente",
"find_bar_type_to_search": "Digita per cercare",
"find_bar_match_case": "Distingui maiuscole/minuscole",
"history_dialog_preview": "Anteprima",
"history_dialog_diff": "Differenze",
"history_dialog_revert_to_selected": "Ripristina alla versione selezionata",
"history_dialog_revert_failed": "Ripristino fallito",
"key_prompt_enter_key": "Inserisci la chiave",
"lock_overlay_locked_due_to_inactivity": "Bloccato per inattività",
"lock_overlay_unlock": "Sblocca",
"main_window_ready": "Pronto",
"main_window_save_a_version": "Salva una versione",
"main_window_settings_accessible_flag": "Impo&stazioni",
"set_an_encryption_key": "Imposta una chiave di crittografia",
"set_an_encryption_key_explanation": "Bouquin cripta i tuoi dati.\n\nCrea una passphrase sicura per criptare il blocco note.\n\nPuoi sempre cambiarla in seguito!",
"unlock_encrypted_notebook": "Sblocca il blocco note criptato",
"unlock_encrypted_notebook_explanation": "Inserisci la chiave per sbloccare il blocco note",
"open_in_new_tab": "Apri in una nuova scheda",
"autosave": "salvataggio automatico",
"unchecked_checkbox_items_moved_to_next_day": "Le caselle non spuntate sono state spostate al giorno successivo",
"move_yesterdays_unchecked_todos_to_today_on_startup": "Sposta i TODO non completati di ieri a oggi all'avvio",
"insert_images": "Inserisci immagini",
"images": "Immagini",
"reopen_failed": "Riapertura fallita",
"unlock_failed": "Sblocco fallito",
"could_not_unlock_database_at_new_path": "Impossibile sbloccare il database nel nuovo percorso.",
"unencrypted_export": "Esportazione non criptata",
"unencrypted_export_warning": "L'esportazione del database sarà non criptata!\nVuoi davvero continuare?\nSe desideri un backup criptato, scegli Backup invece di Esporta.",
"unrecognised_extension": "Estensione non riconosciuta!",
"backup_encrypted_notebook": "Backup del blocco note criptato",
"enter_a_name_for_this_version": "Inserisci un nome per questa versione",
"new_version_i_saved_at": "Nuova versione salvata il",
"save_key_warning": "Se non vuoi che ti venga richiesta la chiave di crittografia, seleziona questa opzione per ricordarla.\nATTENZIONE: la chiave viene salvata sul disco e potrebbe essere recuperabile se il disco fosse compromesso.",
"lock_screen_when_idle": "Blocca lo schermo quando inattivo",
"autolock_explanation": "Bouquin bloccherà automaticamente il blocco note dopo questo intervallo di tempo, dopodiché sarà necessario reinserire la chiave per sbloccarlo.\nImposta a 0 (mai) per non bloccarlo mai.",
"search_for_notes_here": "Cerca note qui",
"toolbar_format": "Formato",
"toolbar_bold": "Grassetto",
"toolbar_italic": "Corsivo",
"toolbar_strikethrough": "Barrato",
"toolbar_normal_paragraph_text": "Testo normale",
"toolbar_bulleted_list": "Elenco puntato",
"toolbar_numbered_list": "Elenco numerato",
"toolbar_code_block": "Blocco di codice",
"toolbar_heading": "Titolo",
"toolbar_toggle_checkboxes": "Attiva/disattiva caselle di controllo"
}

View file

@ -12,6 +12,7 @@ from PySide6.QtGui import (
QTextCursor, QTextCursor,
QTextDocument, QTextDocument,
QTextFormat, QTextFormat,
QTextBlockFormat,
QTextImageFormat, QTextImageFormat,
) )
from PySide6.QtCore import Qt, QRect, QTimer from PySide6.QtCore import Qt, QRect, QTimer
@ -43,6 +44,8 @@ class MarkdownEditor(QTextEdit):
font.setPointSize(10) font.setPointSize(10)
self.setFont(font) self.setFont(font)
self._apply_line_spacing() # 1.25× initial spacing
# Checkbox characters (Unicode for display, markdown for storage) # Checkbox characters (Unicode for display, markdown for storage)
self._CHECK_UNCHECKED_DISPLAY = "" self._CHECK_UNCHECKED_DISPLAY = ""
self._CHECK_CHECKED_DISPLAY = "" self._CHECK_CHECKED_DISPLAY = ""
@ -73,6 +76,8 @@ class MarkdownEditor(QTextEdit):
# reattach the highlighter to the new document # reattach the highlighter to the new document
if hasattr(self, "highlighter") and self.highlighter: if hasattr(self, "highlighter") and self.highlighter:
self.highlighter.setDocument(self.document()) self.highlighter.setDocument(self.document())
self._apply_line_spacing()
self._apply_code_block_spacing()
QTimer.singleShot(0, self._update_code_block_row_backgrounds) QTimer.singleShot(0, self._update_code_block_row_backgrounds)
def showEvent(self, e): def showEvent(self, e):
@ -140,9 +145,10 @@ class MarkdownEditor(QTextEdit):
def _update_code_block_row_backgrounds(self): def _update_code_block_row_backgrounds(self):
"""Paint a full-width background for each line that is in a fenced code block.""" """Paint a full-width background for each line that is in a fenced code block."""
doc = self.document() doc = self.document()
sels = [] if doc is None:
return
# Use the same bg color as the highlighter's code block sels = []
bg_brush = self.highlighter.code_block_format.background() bg_brush = self.highlighter.code_block_format.background()
inside = False inside = False
@ -158,16 +164,12 @@ class MarkdownEditor(QTextEdit):
fmt = QTextCharFormat() fmt = QTextCharFormat()
fmt.setBackground(bg_brush) fmt.setBackground(bg_brush)
fmt.setProperty(QTextFormat.FullWidthSelection, True) fmt.setProperty(QTextFormat.FullWidthSelection, True)
# mark so we can merge with other selections safely
fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg") fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg")
sel.format = fmt sel.format = fmt
cur = QTextCursor(doc) cur = QTextCursor(doc)
cur.setPosition( cur.setPosition(block.position())
block.position()
) # collapsed cursor = whole line when FullWidthSelection
sel.cursor = cur sel.cursor = cur
sels.append(sel) sels.append(sel)
if is_fence: if is_fence:
@ -182,6 +184,60 @@ class MarkdownEditor(QTextEdit):
] ]
self.setExtraSelections(others + sels) self.setExtraSelections(others + sels)
def _apply_line_spacing(self, height: float = 125.0):
"""Apply proportional line spacing to the whole document."""
doc = self.document()
if doc is None:
return
cursor = QTextCursor(doc)
cursor.beginEditBlock()
cursor.select(QTextCursor.Document)
fmt = QTextBlockFormat()
fmt.setLineHeight(
height, # 125.0 = 1.25×
QTextBlockFormat.LineHeightTypes.ProportionalHeight.value,
)
cursor.mergeBlockFormat(fmt)
cursor.endEditBlock()
def _apply_code_block_spacing(self):
"""
Make all fenced code-block lines (including ``` fences) single-spaced.
Call this AFTER _apply_line_spacing().
"""
doc = self.document()
if doc is None:
return
cursor = QTextCursor(doc)
cursor.beginEditBlock()
inside = False
block = doc.begin()
while block.isValid():
text = block.text()
stripped = text.strip()
is_fence = stripped.startswith("```")
is_code_line = is_fence or inside
if is_code_line:
fmt = block.blockFormat()
fmt.setLineHeight(
0.0,
QTextBlockFormat.LineHeightTypes.SingleHeight.value,
)
cursor.setPosition(block.position())
cursor.setBlockFormat(fmt)
if is_fence:
inside = not inside
block = block.next()
cursor.endEditBlock()
def to_markdown(self) -> str: def to_markdown(self) -> str:
"""Export current content as markdown.""" """Export current content as markdown."""
# First, extract any embedded images and convert to markdown # First, extract any embedded images and convert to markdown
@ -255,6 +311,9 @@ class MarkdownEditor(QTextEdit):
finally: finally:
self._updating = False self._updating = False
self._apply_line_spacing()
self._apply_code_block_spacing()
# Render any embedded images # Render any embedded images
self._render_images() self._render_images()
@ -452,9 +511,33 @@ class MarkdownEditor(QTextEdit):
block_state = current_block.userState() block_state = current_block.userState()
# If current line is opening code fence, or we're inside a code block stripped = current_line.strip()
if current_line.strip().startswith("```") or block_state == 1: is_fence_line = stripped.startswith("```")
# Just insert a regular newline - the highlighter will format it as code
if is_fence_line:
# Work out if this fence is closing (inside block before it)
inside_before = self._is_inside_code_block(current_block.previous())
# Insert the newline as usual
super().keyPressEvent(event)
if inside_before:
# We were on the *closing* fence; the new line is outside the block.
# Give that new block normal 1.25× spacing.
new_block = self.textCursor().block()
fmt = new_block.blockFormat()
fmt.setLineHeight(
125.0,
QTextBlockFormat.LineHeightTypes.ProportionalHeight.value,
)
cur2 = self.textCursor()
cur2.setBlockFormat(fmt)
self.setTextCursor(cur2)
return
# Inside a code block (but not on a fence): newline stays code-style
if block_state == 1:
super().keyPressEvent(event) super().keyPressEvent(event)
return return
@ -646,6 +729,9 @@ class MarkdownEditor(QTextEdit):
c.insertText(f"```\n{selected.rstrip()}\n```\n") c.insertText(f"```\n{selected.rstrip()}\n```\n")
if hasattr(self, "_update_code_block_row_backgrounds"): if hasattr(self, "_update_code_block_row_backgrounds"):
self._update_code_block_row_backgrounds() self._update_code_block_row_backgrounds()
# tighten spacing for the new code block
self._apply_code_block_spacing()
self.setFocus() self.setFocus()
return return
@ -710,6 +796,10 @@ class MarkdownEditor(QTextEdit):
if hasattr(self, "_update_code_block_row_backgrounds"): if hasattr(self, "_update_code_block_row_backgrounds"):
self._update_code_block_row_backgrounds() self._update_code_block_row_backgrounds()
# tighten spacing for the new code block
self._apply_code_block_spacing()
self.setFocus() self.setFocus()
def apply_heading(self, size: int): def apply_heading(self, size: int):

View file

@ -5,3 +5,13 @@ def test_load_strings_uses_system_locale_and_fallback():
# pass a bogus locale to trigger fallback-to-default # pass a bogus locale to trigger fallback-to-default
strings.load_strings("zz") strings.load_strings("zz")
assert strings._("next") # key exists in base translations assert strings._("next") # key exists in base translations
def test_load_strings_french():
strings.load_strings("fr")
assert strings._("today") == "Aujourd'hui" # translation exists in French
def test_load_strings_italian():
strings.load_strings("it")
assert strings._("today") == "Oggi" # translation exists in Italian