Releases: 0xra0/bethesda-strings-editor
v0.2.3
[v0.2.3] — 2026-06-21
Added
- Add NexusMods Translation Browser: search, download, and import as TM
New gui/nexusmods_client.py provides a thin NexusMods v1 API + search
wrapper (search, mod files, download links, streaming download).
New gui/nexusmods_browser_dialog.py is a two-panel QDialog: left panel
has game selector + search box + results table; right panel shows mod
detail, file list, and three actions — open mod page in browser,
Download & Import as TM, Download & Merge into Current. Zip archives
are automatically unpacked to find .strings/.dlstrings/.ilstrings files.
Free-account download 403s are handled gracefully by opening the mod
page in the browser as a fallback.
TranslationMemory.load_strings_file() added to load a BethesdaStringFile
directly as a TM source by string ID.
Wired into File → Browse NexusMods for Translations… in MainWindow;
imported TMs are applied to the active worker and any open file via
the existing import_translations() path.
Co-Authored-By: Claude Sonnet 4.6 [email protected](acbb0c4)
- Add Visual Context Preview: render strings in Bethesda UI boxes with game fonts
New gui/visual_context_preview.py is a dockable panel (View → Visual
Context Preview, Ctrl+Shift+P) that renders the current string inside
a faithful simulation of the Starfield in-game UI box so translators
can see exactly how text will wrap and whether it overflows the box.
Font setup: RF_35_M (Cyrillic body), RF_55_M (Cyrillic bold),
NB_Architekt_Light (Latin body), NB_Architekt (Latin bold) — all
extracted from fonts_en.swf / fonts_uk.swf via JPEXS and stored in
data/fonts/. Registered with QFontDatabase at runtime; falls back to
system sans-serif when files are absent.
Context presets (Dialogue 680px/3 lines, Quest 400px, Book 480px/22
lines, Note 440px, Terminal 560px monospace, UI 340px, General 520px)
are auto-detected from StringType and can be overridden by the user.
Box dimensions match Starfield's 1280×720 native Scaleform canvas.
Per-string stats bar shows char delta (Src/Trl/%), line count, and a
colour-coded ✓ Fits / OVERFLOW badge. Overflow lines are tinted red
inside the box; an OVERFLOW badge appears when the text exceeds the
box height. Source / Translation / Both view modes available.
Wired into MainWindow._on_selection_changed(); updates only when the
panel is visible (no cost when hidden).
Co-Authored-By: Claude Sonnet 4.6 [email protected](6694774)
- Add _restore_missing_newlines to translation pipeline
When the Ollama model merges two lines into one (e.g. translates
"Line1.\nLine2." as "Line1. Line2."), the pipeline now detects the
dropped newline and restores it using proportional positioning —
advancing past sentence-ending punctuation and absorbing the
inter-sentence space the model inserted.
Applied at three sites:
- Batch-loop cache hit: corrects already-cached bad translations
before emitting translation_ready, so a previous bad result is
silently fixed without requiring a manual retranslate. - _translate_single single-call post-processing: covers strings that
bypass the line-by-line path (e.g. lines > 200 chars). - _translate_chunked post-processing: covers chunked translations.
The line-by-line path (>= 2 lines, each < 200 chars) still takes
priority and preserves newlines by construction; this is a safety net
for the cases that fall through to a single model call.
Co-Authored-By: Claude Sonnet 4.6 [email protected](d32bc0a)
- Add UI Constraint Enforcer: flag translations >40% longer than English original
Introduce UI_OVERFLOW QC code (WARNING severity) that fires when a translation
exceeds 1.4× the English source length on strings of 20+ characters — the
threshold at which Bethesda hardcoded UI bounding boxes (dialogue panels,
pip-boy entries, loading-screen tips) begin to clip text.
- UI_OVERFLOW_RATIO = 1.40 constant at module level for easy tuning
- Detail field carries an exact character budget (orig×1.4) and the overshoot
so the retry hint gives the AI a concrete target: "shorten by N+ chars" - Added to RETRANSLATE_CODES so Auto-Retranslate Issues picks it up
automatically and re-queues the string with a "shorten while retaining
meaning" prompt before it reaches a human reviewer - Short originals (< 20 chars) are excluded — single words and short labels
have noisy ratios and are unlikely to be clipped by a few extra characters
(they fall through to the existing LENGTH_INCREASE / SUSPICIOUSLY_LONG codes)
Co-Authored-By: Claude Sonnet 4.6 [email protected](ff306fa)
- Add Vim Macro Recording for batch string operations
MacroRecorder stores a sequence of steps (regex replace, set status) and
replays them against StringTableModel rows in one batch — fixing repetitive
formatting issues across thousands of strings without manual per-row edits.
UI: MacroDialog (Ctrl+M or 'q' in table) lets users build a step list, preview
match count before applying, choose scope (all/translated/pending/selected),
and play with a cancellable progress dialog. '@' key replays the last macro on
the focused row. Both actions are registered in KeyboardManager/CommandPalette.
Co-Authored-By: Claude Sonnet 4.6 [email protected](3b938d7)
- Add micro-animations: progress bar pulse and QA success badge
On successful batch translation (zero failures): the progress bar flashes green
and does a two-pulse opacity animation (1→0.5→1→0.5→1→0 over ~1.1s) before
hiding, giving clear visual confirmation that the batch completed cleanly.
On manual quality check (Ctrl+F7) with zero issues: skips the empty dialog and
instead shows a floating "✓ Quality check passed" toast that fades in (220ms),
holds (1.5s), and fades out (450ms), then self-destructs. More satisfying than
an empty results window.
Co-Authored-By: Claude Sonnet 4.6 [email protected](fdfb415)
- Add 492 unfinished translation placeholders for new UI strings across all 6 locales
New GUI dialogs and features added since last translation pass (audio preview,
advanced search/replace, NexusMods browser, macro recorder, dialogue tree,
spell checker, focus overlay, weblate sync, etc.) now have empty
<translation type="unfinished"> entries in all .ts files so translators
can fill them in. Recompiled all .qm binaries.
Co-Authored-By: Claude Sonnet 4.6 [email protected](6dbc0f5)
- Add ти/ви register consistency checker (Ctrl+Alt+R)
New Quality menu entry "Check Register (ти/ви)…" scans all translated
strings and groups them by inferred NPC speaker (EDID prefix in ESP mode,
whole-file bucket for .strings/.dlstrings). Any speaker whose lines mix
informal ти-address (ти/тебе/твій…) with formal ви-address (ви/вас/ваш…)
is flagged. The results dialog shows a speaker table with counts and two
side-by-side evidence panels with one-click jump-to-row navigation.
Co-Authored-By: Claude Sonnet 4.6 [email protected](2cff1ce)
- Add Ukrainian gender agreement checker (Ctrl+Alt+G)
New Quality menu entry "Check Gender Agreement…" detects adjective/noun
gender mismatches in translated Ukrainian strings. Covers adj→noun and
noun←adj (predicative) patterns.
Implementation:
- gender_checker.py: curated ~250-noun gender dictionary (M/F/N) plus
determiner/possessive table (цей/ця/це, наш/наша/наше, etc.).
Masculine adj detection: -ий/-ій suffix (reliable, no verb collision).
Neuter adj detection: -е/-є suffix with stem-validation against the
Ukrainian word list (e.g. "переможе"→"переможий"✗ = verb, not adj). - gender_dialog.py: splitter dialog — mismatch table + HTML context
preview with adj/noun highlighted, jump-to-row navigation. - main_window.py: Ctrl+Alt+G action wired in Quality menu, enabled
alongside other file-level checks.
Co-Authored-By: Claude Sonnet 4.6 [email protected](bfa01e4)
- Add named Translation Sessions (Ctrl+Shift+N / Ctrl+Shift+S)
New "Sessions" top-level menu lets users save and resume named work
contexts that are completely separate from crash-recovery snapshots.
What a session captures:
• Session name + optional note
• Source file path and type
• Current cursor row and vertical scroll position
• Last advanced-search filter (query, column, status, flags)
• Set of string IDs translated during this session (tracked live
via table_model.dataChanged; only strings newly-translated after
the session was started are counted)
Session storage:
• gui/session_manager.py — WorkSession / SearchState dataclasses +
SessionStore (one JSON file per session in
~/.config/BethesdaModTools/sessions/.json)
• Atomic write (tmp → rename), version-tagged, slug-deduplication
Session dialog (gui/session_dialog.py):
• SessionManagerDialog — table of all sessions with per-session
translated count, last-modified date, detail panel; Resume /
Rename / Delete actions; rename propagated to store on close
• NewSessionDialog / RenameSessionDialog
Main-window integration:
• New Session (Ctrl+Shift+N) — snapshots baseline translated IDs
• Save Session (Ctrl+Shift+S) — writes cursor + scroll + file path
• Save Session As…...
v0.2.2
[v0.2.2] — 2026-06-11
Added
- Add anthropic to requirements.txt to fix pyright CI
The anthropic package was used in gui/claude_client.py but missing from
requirements.txt, causing pyright to report reportMissingImports in CI.
Co-Authored-By: Claude Sonnet 4.6 [email protected](bd60038)
- Add Modelfile.qc for fine-tuned Gemma 4 E4B quality-check model
Co-Authored-By: Claude Sonnet 4.6 [email protected](e283b06)
- Add protect_named_entities setting; default to not protecting proper nouns
Previously, faction/company/ship/character/location/creature/resource/system/
ui/game_term categories were only excluded when the source language was
English. For all other source languages, the term protector would replace
these names with opaque tokens, preventing the AI from translating them
(e.g. "United Colonies" could never become "Об'єднані колонії").
Changes:
- term_protector.py: export SOFT_CATEGORIES frozenset listing all proper-
noun/lore categories. This is the single source of truth used everywhere. - app_settings.py: new protect_named_entities: bool = False field.
Config version bumped 22→23 with migration. - ollama_worker.py: remove the source-lang gate; instead exclude
SOFT_CATEGORIES unless self.protect_named_entities is True.
Added protect_named_entities param to init and update_config. - claude_translation_worker.py: same logic; also fixes a latent bug
where protect() (non-existent method) was called instead of protect_text(),
causing term protection to silently fail with AttributeError every time. - settings_dialog.py: new checkbox "Protect proper nouns and lore terms"
(unchecked by default) with tooltip explaining the trade-off. Updated
the stale info label that listed faction names as always protected. - main_window.py: pass protect_named_entities when constructing both
worker types and in update_config.
Format tags, game IDs, XML/alias tokens, structural newline tokens, and
user-added custom terms are always protected regardless of this setting.
Co-Authored-By: Claude Sonnet 4.6 [email protected](6928b7c)
- Add Hunspell spell-check QC check for translated strings
New gui/spell_checker.py wraps hunspell with three fallback backends:
- hunspell pip package (C bindings, fastest)
- spylls pip package (pure Python Hunspell)
- hunspell CLI subprocess (no Python package needed)
Silently disables per-language when no dictionary is installed.
Fires SPELL_ERROR (warning) QC code when misspelled lowercase words are
found; skips ALL-CAPS, proper-noun-capitalised, and digit tokens to keep
false positives low in game text.
Retry hint lists the misspelled words so the model can correct them on
retranslation.
Co-Authored-By: Claude Sonnet 4.6 [email protected](0873e67)
- Add NexusMods BBCode description (no markdown separators)
Co-Authored-By: Claude Sonnet 4.6 [email protected](7ee671e)
- Add config directory override in Settings → Storage
Reads from BSE_CONFIG_DIR env var or a bootstrap file at the fixed
default location, so the redirect is discoverable before the config
dir is known. UI shows the active path, browse/reset buttons, and a
restart-required warning when the value changes.
Co-Authored-By: Claude Sonnet 4.6 [email protected](fe63557)
- Add cache directory override in Settings → Storage
Same bootstrap-file pattern as the config dir override. Priority:
BSE_CACHE_DIR env var → ~/.config/BethesdaModTools/.cache_dir_override
→ /mnt/ssd when mounted → config dir fallback.
UI shows the active cache path, browse/reset buttons, and restart
warning in the Storage group.
Co-Authored-By: Claude Sonnet 4.6 [email protected](6e2b8ff)
-
Add word checkers for DE/ES/FR/IT/PL/PT-BR languages
-
gui/_word_checker_base.py: shared WordChecker class (lazy load, thread-safe,
handles both plain and "word count" frequency-list formats) -
gui/{de,es,fr,it,pl,ptbr}_word_checker.py: thin wrappers per language
-
data/{german,spanish,french,italian,polish,portuguese}_words.txt: 50k-word
frequency lists from hermitdave/FrequencyWords (MIT) -
scripts/download_lang_dicts.py: download/refresh all 6 word lists
-
quality_checker: add _check_latin_coverage() — fires LOW_TARGET_COVERAGE
when <20% of tokens match the expected target language; wired into check() -
ollama_worker: preload all 6 dictionaries on worker init
Co-Authored-By: Claude Sonnet 4.6 [email protected](b089218)
-
Add file association support for all supported file types
-
main.py: open a file path passed as argv[1] via QTimer.singleShot so
double-clicking a .strings/.esp/.ba2 in a file manager launches the app
and loads the file immediately -
aur/bethesda-strings-editor.desktop: add %f to Exec= and declare
MimeType= for the three new custom MIME types -
aur/bethesda-strings-editor-mime.xml: freedesktop MIME definitions for
application/x-bethesda-strings (.strings/.dlstrings/.ilstrings),
application/x-bethesda-plugin (.esp/.esm/.esl, magic TES4), and
application/x-bethesda-archive (.ba2, magic BTDX) -
aur/PKGBUILD: install MIME XML to /usr/share/mime/packages/ so the
shared-mime-info pacman hook runs update-mime-database automatically -
scripts/install_file_associations.sh: helper for source-checkout users
(xdg-mime + desktop-file-install for per-user ~/.local install) -
Modelfile: update FROM to use blob SHA256 reference
Co-Authored-By: Claude Sonnet 4.6 [email protected](11e68ba)
- Add Modelfile and config for gemma4-opus48-st
Modelfile.gemma4-opus48: Ollama model definition for the Gemma 4 12B IT
fine-tuned on Claude Opus 4.6/4.7/4.8 reasoning distillation (GGUF at
/mnt/ssd/models/gguf/gemma4-opus48-Q4_K_M.gguf). Uses Gemma 4 chat
template, think=disabled, top_k 64 / top_p 0.95 per author recommendation.
MODEL_CONFIGS["gemma4-opus48-st"]: matching runtime config in OllamaWorker
so the app sends correct parameters when this model is selected.
Also adds gemma4-opus48-st to the default model picker suggestions.
Co-Authored-By: Claude Sonnet 4.6 [email protected](96a1a36)
- Add rule 3e: translate multi-sentence bracket content (publisher notes, etc.)
The four existing categories only covered single-word tokens. Long bracket
content like publisher notes and editorial remarks had no matching rule, so
the model defaulted to preserving them in English. New category (e) explicitly
instructs: translate any bracket text that is multiple words or full sentences,
keeping the [ ] delimiters, with a concrete example from the failing book string.
Co-Authored-By: Claude Sonnet 4.6 [email protected](a9d2398)
- Add skip-string-types setting (Book, Note, Dialogue, etc.)
Adds a row of checkboxes in Settings → Translation for each content
type (Book, Note, Terminal, Dialogue, Quest, UI, System). Checked
types are skipped during AI batch translation — strings are left
untranslated and marked as pending.
Implementation:
- AppSettings.skip_string_types: list (default [], CONFIG_VERSION 24)
- OllamaWorker / ClaudeTranslationWorker: skipped_types attribute;
classify() called per-string; returns SKIP_SIGNAL when matched.
Paragraph sub-requests (string_id==-1) are exempt from the check. - main_window.py passes skip_string_types to both workers on init.
Co-Authored-By: Claude Sonnet 4.6 [email protected](f6677f9)
- Add Find & Replace to Advanced Search dialog
Adds a "Replace (Translated text only)" group below the search criteria:
- "Replace with:" field (supports regex back-references when regex is on)
- "Replace All" button — substitutes in all rows matching the current
search criteria; asks for confirmation when >20 rows are affected - Shows "Replaced N occurrences in M rows" feedback inline
Replace uses the same pattern/flags as search (regex, case, whole-word).
Only the Translated column is modified; Original text is never touched.
Co-Authored-By: Claude Sonnet 4.6 [email protected](2e4e217)
- Add error-code filter to Quality Check Results dialog
Adds an "Error code:" dropdown next to the existing severity filter.
It is populated dynamically from every issue code present in the current
reports (sorted alphabetically), so only codes that actually appear are
listed. Selecting a code hides all rows that don't contain that code.
Both filters (severity + code) are ANDed: e.g. "Errors" + "ENGLISH_LEAK"
shows only error-severity rows that have an ENGLISH_LEAK issue.
The code combo is rebuilt after Auto-Fix / retranslation refresh so
cleared codes disappear and the previous selection is preserved when
possible.
Co-Authored-By: Claude Sonnet 4.6 [email protected]([71bd17d](https://github.com/0xra0/bethesda-strings-editor/commit/...
v0.2.1
[v0.2.1] — 2026-06-02
Added
- Add NexusMods automatic upload to release workflow
Co-Authored-By: Claude Sonnet 4.6 [email protected](65d3b97)
-
Add NexusMods upload API support (v3 multipart flow)
-
gui/nexusmods_uploader.py: 6-step upload client (initiate → chunk PUT
→ S3 XML complete → finalise → poll → create version) -
gui/nexusmods_upload_dialog.py: upload dialog with API key field,
file/group-ID pickers, metadata form, progress bar, QThread worker -
app_settings.py: nexusmods_api_key + nexusmods_file_group_id fields,
config v20 → v21 migration -
main_window.py: File → Upload to NexusMods… menu item
Co-Authored-By: Claude Sonnet 4.6 [email protected](031177c)
-
Add NexusMods page auto-update script and updated description
-
scripts/update_nexusmods_page.py: Playwright automation that logs in,
updates short + full description via TinyMCE API, saves, and persists
cookies for future runs -
resources/nexusmods_description.html: updated copy for all 11 languages,
BA2 support, newline restoration, NexusMods upload feature
Co-Authored-By: Claude Sonnet 4.6 [email protected](65cbd1b)
-
Add content-type icons to string table (Phosphor Icons, theme-aware)
-
gui/string_type_detector.py: classify strings into 7 types (Dialogue,
Quest, Book, Note, Terminal, UI, System) using regex heuristics on
text content + file extension; render Phosphor SVG icons on-demand via
QSvgRenderer, cached per (type, dark_mode); colors adapt to palette -
gui/string_table.py: insert "Kind" column (28 px) after ID; returns
themed QIcon in DecorationRole and label in ToolTipRole; type cache
cleared and icons re-tinted on theme change -
main_window.py: call invalidate_type_cache() after manual and
auto (OS) theme changes
Icon set: Phosphor Icons (MIT), phosphoricons.com
Colours: blue=dialogue, amber=quest, violet=book, slate=note,
green=terminal, orange=UI, slate=system
Co-Authored-By: Claude Sonnet 4.6 [email protected](3f1f155)
- Add NexusMods badge and link to README
Co-Authored-By: Claude Sonnet 4.6 [email protected](8417fc6)
- Add Batch Translate Folder dialog for bulk AI retranslation
New Translation → Batch Translate Folder… menu item opens a dialog that:
- Scans a folder of binary string files (_uk.strings / .dlstrings / .ilstrings)
- Loads matching _ru source files for source-language context
- Auto-fixes mechanical issues (Russian chars, missing tags, whitespace)
- AI-translates untranslated strings via Ollama (parallel workers)
- AI-retranslates strings with Russian word leakage
- Saves fixed binary files in place
- Shows real-time progress and log
Designed for bulk post-fix of the 4,737 untranslated + 5,936 Russian-leak
strings remaining after the scripted fix pass.
Co-Authored-By: Claude Sonnet 4.6 [email protected](24547f8)
- Add Claude AI integration: translation backend, chat assistant, quality review
Three new Claude-powered features (requires Anthropic API key):
-
Claude translation backend
- Select claude-haiku-4-5 / claude-sonnet-4-6 / claude-opus-4-7 in Preferences
- ClaudeTranslationWorker mirrors OllamaWorker signals exactly; all existing
progress, caching, term-protection, and glossary plumbing works unchanged - Parallel API calls via ThreadPoolExecutor (default 5 workers)
-
Claude AI Assistant chat panel (Ctrl+Shift+A)
- Dockable panel (right side); auto-updates context when a string is selected
- Full multi-turn conversation with Claude about the current string
- "Review Translation" — structured quality review with issues + rating
- "Suggest Translation" — asks Claude to translate the source string
- "Use as Translation" — applies Claude's last code-block suggestion to the table
- API key stored in existing SecretStore (encrypted, per-machine)
- Ctrl+Enter to send; model selector (Haiku / Sonnet / Opus)
-
Claude menu
- Claude AI → Show AI Assistant (Ctrl+Shift+A)
- Claude AI → Review Current Translation (Ctrl+Shift+R)
- Claude AI → Suggest Translation (Ctrl+Shift+T)
Co-Authored-By: Claude Sonnet 4.6 [email protected](780e694)
- Add QC training dataset generator for Gemma fine-tuning
scripts/create_qc_dataset.py — scans EN→UK Starfield string pairs,
runs QualityChecker on each, and emits ShareGPT JSONL with structured
quality assessment examples (VERDICT/CODES/SEVERITY/DETAILS/ACTION format).
Eleven synthetic injectors (missing_tag, extra_tag, russian_leak,
ai_artifact, repetition, empty, untranslated, truncated,
suspiciously_short, case_mismatch, missing_newlines) ensure full coverage
of all QC issue codes even when real data has few examples of a code.
Generated qc_dataset_sharegpt.jsonl: 14,928 examples
GOOD: 5,000 | ISSUES_FOUND: 9,928 across 16+ issue codes.
Co-Authored-By: Claude Sonnet 4.6 [email protected](0547e80)
- Add icons to all menu actions and extend toolbar
Sets QIcon.fromTheme() icons on every menu action (File, Edit,
Translation, Glossary, Claude AI, Settings, Help) so menus are no
longer bare text. Also adds Approve, Reject, Next Untranslated,
Edit Glossary, and Show AI Assistant buttons to the toolbar so the
most-used review actions are one click away.
Co-Authored-By: Claude Sonnet 4.6 [email protected](b979d57)
- Add Gemma 4 4B Modelfile and register in OllamaWorker
Modelfile.gemma4 points to the Unsloth Studio Q4_K_M GGUF export of
gemma-4-e4b-it. Adds "translategemma4-st" to OllamaWorker.MODEL_CONFIGS
with the same parameters as translategemma3-st so the app can use it
immediately after: ollama create translategemma4-st -f Modelfile.gemma4
Co-Authored-By: Claude Sonnet 4.6 [email protected](5ebcd5a)
Changed
- Update UI translations: de/fr/es/pl/cs complete, uk partial
de_DE, fr_FR, es_ES, pl_PL, cs_CZ: 844 strings fully translated.
uk_UA: 422 strings translated, 422 new strings pending (added by lupdate).
Co-Authored-By: Claude Sonnet 4.6 [email protected](25f5a8a)
Fixed
- Fix secrets context error in NexusMods upload job
secrets context is not available in job-level if conditions; move the
API key check to step level using an env var instead.
Co-Authored-By: Claude Sonnet 4.6 [email protected](9e224b4)
- Fix Ctrl+Shift+A shortcut conflict breaking both actions
Translate All and Claude panel both claimed Ctrl+Shift+A, silencing
both shortcuts. Reassigned Claude panel to Ctrl+Shift+C.
Co-Authored-By: Claude Sonnet 4.6 [email protected](f5da782)
- Fix encoding detection: English UTF-8 files no longer mis-detected as windows-1252
Two changes:
- Count 3-byte UTF-8 sequences (0xE0-0xEF lead) in addition to 2-byte ones, so
English smart quotes / em-dashes (U+201x, U+2014 etc.) raise UTF-8 confidence
instead of leaving confirmed_pairs at zero. - Add step 5: if UTF-8 strict decode fails but < 1% of decoded chars are
replacement chars, the content is overwhelmingly ASCII — return UTF-8 rather
than falling through to the CP1252 fallback. This handles files that are
effectively UTF-8 but contain a handful of stray CP1252 bytes.
Russian/Ukrainian UTF-8 detection is unaffected (Cyrillic pair check in step 2).
Co-Authored-By: Claude Sonnet 4.6 [email protected](ebfaa63)
-
Fix ruff lint errors: remove unused imports and local re-imports
-
scripts/update_translations.py: remove unused
import json -
gui/claude_translation_worker.py: remove unused
Optionalfrom typing import -
gui/claude_chat_panel.py: remove unused top-level
QApplicationimport;
remove localfrom PySide6.QtGui import QTextCursorre-imports from
_begin_claude_stream / _on_token / _on_reply (F811 redefinition of the
top-level import on line 19)
Co-Authored-By: Claude Sonnet 4.6 [email protected](0e47470)
- Fix NexusMods upload steps being skipped due to env secret check
GitHub Actions does not reliably expose job-level env vars set from secrets
to step if: expressions — env.NEXUSMODS_API_KEY != '' always evaluated false,
silently skipping both upload steps. Remove the per-step guards; the job-level
condition (non-prerelease tag) is sufficient.
Co-Authored-By: Claude Sonnet 4.6 [email protected](a72fc2d)
- Fix NexusMods workflow: use vars. context instead of secrets.
The API key and file group IDs were stored as repository Variables
(Settings → Secrets and variables → Variables tab), not as...
v0.2.0 — Multi-language support & BA2 archives
[v0.2.0] — 2026-05-27
Added
- Add BA2 archive support and fix English→Ukrainian translation skip bug
BA2 archive support (Fallout 4 / Starfield):
- bethesda_strings/ba2_handler.py: pure-Python BA2File class for GNRL
archives; zlib compression; FO4 v1 + Starfield v2 header variants;
safe in-place save via temp-file-then-move - gui/ba2_picker_dialog.py: picker dialog when archive has multiple
strings files - core.py: add BethesdaStringFile.get_bytes() for in-memory serialization
- main_window.py: .ba2 added to drop zones and file filters; open/save
BA2 with full repack; BA2 file handle closed on new open and exit
Fix: English→Ukrainian translation skipped all strings (ESM/BA2)
- _INPUT_NOTRANS_RE included '^[^Ѐ-ӿ]+$' (no Cyrillic = skip), which
matched every English string and blocked EN→UK translation entirely - Moved that pattern to _INPUT_NOTRANS_NOCYRILLIC_RE and apply it only
when source_lang != 'English' at both call sites in OllamaWorker - Also fixed path regex (was ^\w*[\/]\w, only matched 3-char strings)
to ^\w.[/\]..\w+$ which correctly fullmatches long paths
Co-Authored-By: Claude Sonnet 4.6 [email protected](415a399)
- Add all 9 official Starfield languages to source/target selectors
Starfield ships with: English, German, Spanish, French, Italian, Japanese,
Polish, Portuguese (Brazil), Chinese (Simplified) — plus Russian and Ukrainian
for xTranslator workflows.
Changes:
- main_window.py: SUPPORTED_LANGUAGES is now a list of (display_name, locale_code)
tuples; combo boxes store the locale code as item data. Minimum width bumped to
145 px to fit "Portuguese (Brazil)" / "Chinese (Simplified)". All hard-coded
language comparisons updated from display names to locale codes (en/ru/uk/…). - encoding.py: ENCODING_PAIRS expanded with Starfield locale codes as aliases
(de, es, fr, it, ja, pl, ptbr, zhhans, ru, uk). get_encodings_for_locale()
now handles short codes, BCP-47 variants, and full display names. - app_settings.py: CONFIG_VERSION → 20; defaults changed to locale codes (ru/uk);
v20 migration converts stored display names to locale codes. - ollama_worker.py / quality_checker.py: language comparisons updated to locale
codes; Cyrillic-script guard accepts both "uk"/"ru" and legacy display names.
Co-Authored-By: Claude Sonnet 4.6 [email protected](484f9f6)
- Add language-specific Ollama prompts for all 11 supported languages
Previously to_system_prompt() only had two hard-coded branches:
English→Ukrainian and a generic Russian→Ukrainian fallback.
Now it is fully data-driven:
_LANG_DISPLAY – locale code → display name for the "To {Language}:" prompt
_TARGET_STYLE – per-target-language style / register rules (rule #1)
_SOURCE_EXTRA – source-language notes (Russian: "don't transliterate")
_PAIR_EXTRA – extra rules for specific pairs (ru→uk Cyrillic mapping)
_LANG_EXAMPLES – one or two example translations per (src, tgt) pair
covering en→de/es/fr/it/ja/pl/ptbr/zhhans + ru→uk
to_prompt() now maps locale codes back to full display names so the
TranslateGemma English-Anchor fine-tuning format ("To Ukrainian:\n…") is
preserved for every language combination.
Co-Authored-By: Claude Sonnet 4.6 [email protected](356c472)
Changed
- Update app icon to reflect multi-language support
Old icon showed "Ru → Ук" (Russian→Ukrainian only).
New icon shows "EN →" with a 2×2 grid of language chips (DE/FR/УК/JA)
hinting at the full set of 11 supported languages, against the same
NASApunk starfield background. All three formats regenerated:
app_icon.png (512 px), app_icon_64.png, app_icon.ico (multi-size).
Co-Authored-By: Claude Sonnet 4.6 [email protected](e478481)
- Update NexusMods header to reflect multi-language support
Replaces old "Russian → Ukrainian" subtitle with an "EN →" row of
coloured language chips: DE ES FR IT JA PL PT-BR ZH RU UK.
Feature tag row updated: added .ilstrings and BA2.
Background keeps the NASApunk starfield + hex-grid aesthetic.
Co-Authored-By: Claude Sonnet 4.6 [email protected](ab031a8)
-
Update README and GitHub description for multi-language support
-
Drop "Russian → Ukrainian only" framing throughout
-
Add supported-languages table (all 11 codes with display names)
-
Document language-pair prompts, newline/spacing restoration,
mixed-script repair, BA2 support, and ShareGPT dataset script -
Add nexusmods_header.png banner at the top
-
Update encoding section (CP1250 Polish, GBK Chinese)
-
Update project structure (ba2_handler.py, extract_sharegpt_dataset.py,
app_settings v20) -
GitHub description and topics updated via API
Co-Authored-By: Claude Sonnet 4.6 [email protected](4d47325)
Fixed
-
Fix token leak, mixed-script repair, and quality checker tag detection
-
term_protector: _normalize_tokens now handles uppercase hex hash,
space-as-underscore separator, and Cyrillic Т/К homoglyphs in token
prefix; adds hash+index fallback key for fully garbled prefixes -
term_protector: _merge_whitespace no longer falls back to source-language
text when translated slot is empty (was injecting English into Ukrainian) -
term_protector: restore_text skips template approach when model drops all
tokens, avoiding mixed-language output; MISSING_TAG QC flags the issue -
ollama_worker: add _fix_mixed_script() to convert stray Latin letters
inside predominantly-Cyrillic words (e.g. "dослідницький" → "дослідницький") -
quality_checker: fix double-counting via span deduplication;
add [M]/[F]/[N] single-char bracket tags and [tk_...] token detection -
scripts: add extract_sharegpt_dataset.py for EN→UK ShareGPT JSONL export
Co-Authored-By: Claude Sonnet 4.6 [email protected](70fa99d)
Other
- Restore dropped newlines and per-line leading spaces after translation
translategemma3-st silently drops [[STRUCT_BREAK_SGL_N]] / [[STRUCT_BREAK_DBL_N]]
tokens, collapsing multi-line strings into a single flat paragraph.
Add _restore_line_structure(): after restore_text() detects the newline
count is lower than the original, the function:
- Splits the original on (\n\n+|\n) to capture the exact delimiter sequence.
- Flattens the translated text and proportionally splits it into N segments
using right-first word-boundary snapping (ensures cut lands after the
segment's last word, not before the first word of the next segment). - Copies per-line leading whitespace from the corresponding original line.
- Rejoins with the original \n / \n\n delimiters.
Empty trailing segments (strings ending with \n) are preserved unchanged.
The function is a no-op when the translated text already has the correct
newline count or the original has no newlines at all.
Co-Authored-By: Claude Sonnet 4.6 [email protected](0369ac1)
- Release v0.2.0: multi-language support, BA2, newline restoration
Co-Authored-By: Claude Sonnet 4.6 [email protected](0065239)
v0.1.1
[v0.1.1] — 2026-05-20
Added
- Add ruff/Pyright static analysis; fix all lint errors
Tooling:
- pyproject.toml: ruff (E/W/F rules, E741/E731/E501 ignored) + mypy config
- .pre-commit-config.yaml: ruff linter + formatter on every commit
- .github/workflows/lint.yml: ruff + Pyright on every PR and main push
Fixes applied:
- ruff --fix: 44 auto-fixed issues (33 unused imports, 9 empty f-strings,
2 redefined-while-unused across the codebase) - ruff --unsafe-fixes: 4 W293 trailing whitespace in docstrings
(bethesda_strings/encoding.py) - F841 dead code: removed unused
qs,has_errors,translated_count
variables in app_settings.py and main_window.py - Pyright reportOptionalMemberAccess: narrowed QApplication.instance()
result before calling .styleHints() in main_window.py; refactored
_auto_fix_selected in quality_dialog.py to use local variables so
Pyright can track the not-None guarantee from the early-return guard
Result: ruff clean (0 errors), Pyright clean (0 errors, 0 warnings).
Co-Authored-By: Claude Sonnet 4.6 [email protected](d262449)
- Add test CI, structured changelog, version injection, AUR package
Test workflow (.github/workflows/test.yml):
- Runs pytest on ubuntu-22.04 and windows-latest on every PR and main push
- Sets QT_QPA_PLATFORM=offscreen on Linux for headless Qt tests
Changelog (release workflow + .cliff.toml):
- Uses git-cliff to generate a structured changelog from commit messages
- Commit prefixes (Add/Fix/Remove/Update/Refactor) map to changelog sections
- Release body is now the cliff-generated notes instead of GitHub's auto-notes
Version injection:
- _version.py: dev placeholder committed to repo
- Release workflow overwrites _version.py with the tag version before
PyInstaller bundles the app, so the frozen binary shows the correct version - main.py: reads version from _version.py with ImportError fallback to "dev"
AUR package (aur/PKGBUILD):
- bethesda-strings-editor-bin: installs the pre-built Linux zip from releases
- Wrapper at /usr/bin/bethesda-strings-editor, desktop entry, icon
- Self-contained (PyInstaller bundle needs no Python/Qt runtime)
- Update sha256sums with
updpkgsumsbefore publishing to AUR
Windows code signing:
- Placeholder in release.yml (commented out) showing the signtool invocation
- Requires DigiCert/Sectigo EV certificate stored in GitHub Secrets
Co-Authored-By: Claude Sonnet 4.6 [email protected](29caa98)
-
Add Sphinx documentation and GitHub Pages deployment
-
docs/: Sphinx project with RTD theme, autodoc for bethesda_strings/
-
docs/format-spec.rst: binary layout tables for .strings/.dlstrings/.ilstrings and SST XML
-
docs/architecture.rst: component diagram, translation pipeline, quality checks table
-
docs/contributing.rst + CONTRIBUTING.md: dev setup, release process, commit conventions
-
.github/workflows/docs.yml: build on every push; deploy to Pages on main
-
Fix RST formatting in xml_handler.py module docstring (clean sphinx-build -W)
Co-Authored-By: Claude Sonnet 4.6 [email protected](41084fb)
- Add DeepWiki badge to README
Co-Authored-By: Claude Sonnet 4.6 [email protected](458d82a)
- Add CHANGELOG.md
Co-Authored-By: Claude Sonnet 4.6 [email protected](48fc995)
-
Add multi-language UI support (de, es, fr, pl, cs + RTL infrastructure)
-
Skeleton .ts files for German, Spanish, French, Polish, Czech
-
RTL layout auto-enabled for Arabic/Hebrew/Farsi/Urdu locales
-
Language selector in Settings shows all locales with native names
-
Restart-required notice appears inline on language change
-
ui_language migrated from display names to BCP-47 locale codes (v17)
-
Generic translator loader in main.py replaces Ukrainian-only branch
-
compile_translations.sh compiles all *.ts files, not just uk_UA
-
PyInstaller spec globs all *.qm at build time
-
TRANSLATING.md contributor guide + .weblate/component.yml
Co-Authored-By: Claude Sonnet 4.6 [email protected](e16c30a)
-
Add accessibility: High Contrast theme, focus indicators, font size, color-blind mode
-
High Contrast theme: WCAG AAA black/white/cyan with yellow focus rings
-
Focus indicator QSS mixin applied to every theme (buttons, checkboxes,
tabs, list/table views) — previously only QLineEdit/QComboBox/QSpinBox -
Qt.AccessibleTextRole in StringTableModel: screen readers get
"Translated — quality error" instead of raw symbols -
Font size setting (0=OS default, 8-24pt); applied via QApplication.setFont()
-
Color-blind mode: blue/orange replaces green/red in status column;
symbols (✓/⚠/✗) convey state independently of color -
Config version 17 → 18
Co-Authored-By: Claude Sonnet 4.6 [email protected](1108316)
-
Add encryption, audit logging, and security settings (v0.1.1)
-
AES-256-GCM at-rest encryption for translation cache (opt-in)
-
SecretStore: system keyring with PBKDF2/machine-key fallback
-
Append-only JSON-lines security audit log with 5 MB rotation
-
Settings → Security section: encrypt cache + audit log toggles
-
Accessibility: High Contrast theme, focus indicators, font size, color-blind mode
-
Multi-language UI: de/es/fr/pl/cs skeletons, RTL support, Weblate config
-
Glossary editor O(N²) freeze fix
-
cryptography>=43.0 + keyring>=25.0 dependencies
Co-Authored-By: Claude Sonnet 4.6 [email protected](622c891)
Fixed
-
Fix CI: pyright path install, libEGL on Linux, Windows UTF-8 in tests
-
lint.yml: install pyright via pip so version:path finds it
-
test.yml: apt-get libegl1 on ubuntu-22.04 (PySide6 needs libEGL.so.1)
-
test_glossary.py: specify encoding='utf-8' for write_text/read_text
(Windows defaults to cp1252 which can't encode Cyrillic)
Co-Authored-By: Claude Sonnet 4.6 [email protected](f279d5f)
- Fix lint: exclude scripts/ from Pyright (PIL not installed in CI)
Co-Authored-By: Claude Sonnet 4.6 [email protected](62f38d2)
- Fix glossary editor freeze: rebuild search index once, not per-entry
_clone_glossary called add_entry() N times, each triggering a full
_rebuild_search_index() — O(N²) for large glossaries. Add _rebuild=False
flag to add_entry and call _rebuild_search_index() once after bulk insert.
Co-Authored-By: Claude Sonnet 4.6 [email protected](3a94a58)
- Fix git-cliff template: remote.link -> remote.github.link (v2.x)
Co-Authored-By: Claude Sonnet 4.6 [email protected](9572e60)
- Fix git-cliff template: use hardcoded repo URL instead of remote.link
Co-Authored-By: Claude Sonnet 4.6 [email protected](c4bae28)