Merge branch 'main' into tags
This commit is contained in:
commit
df7ae0b42d
4 changed files with 226 additions and 10 deletions
|
|
@ -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
114
bouquin/locales/it.json
Normal 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"
|
||||||
|
}
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue