Introduces a two-language (EN/VI) i18n system with hot-swap support.
All ~160 user-facing strings are centralised in gui/locales.py; views
retranslate in-place on language switch without restarting the app.
- gui/locales.py: new file — _EN/_VI dicts, t() lookup, set_language(),
get_language(); assert guard ensures EN/VI key parity
- gui/app.py: switch_language(), _apply_startup_language(),
_save_language_pref(), _rebuild_nav_labels(); language stored in
config.json under ui.language; pipeline step headers and run_tool
status lines translated
- gui/views/settings.py: Language dropdown card (English / Tiếng Việt)
- gui/views/dashboard.py: all strings via t(); static header widgets
stored and retranslated in refresh()
- gui/views/mods.py: all strings via t(); _STATUS dict built at call
time so server status labels update on language switch
- gui/views/tools.py: all strings via _translatable registry; tab names
and segmented-button values kept in English (CTkTabview constraint)
- gui/views/logs.py: title + Copy/Clear buttons stored, retranslated
- gui/wizard.py: all 3 pages fully translated
- docs/huong-dan-su-dung.md: full Vietnamese user guide
- CLAUDE.md: documents localization architecture and constraints
subprocess.run with capture_output=True blocked until process exit,
dumping all output at once. Now uses Popen with line-by-line reading,
-u flag, and PYTHONUNBUFFERED=1 so logs stream in real time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_find_folder() used a plain lowercase compare which failed when the
server canonical folder name differs from the comparison.json mod name
in more than just case (e.g. spaces vs underscores: "NIArms All in One"
vs "@NIArms_All_In_One"). These mods showed ✗ even though the pipeline
found and linked them correctly.
Add a normalized-name fallback (strips non-alphanumeric, same logic as
fetcher._normalize_name) so the lookup matches the same way the fetcher
resolves mods from the server index.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
comparison.json was stale (built from 2 test presets only) while
modlist_html/ now has 4 files. Regenerated comparison.json from all
presets to restore consistency.
Also added _SkipTest exception to the test harness so the
consistency check skips gracefully on a fresh clone (comparison.json
is gitignored) instead of raising AssertionError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers: common commands, package vs CLI architecture, data flow,
group naming convention, server index structure, junction/symlink
rules (critical: never shutil.rmtree), check_names two-pass
classification, Python 3.9 from __future__ requirement, and
test suite structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Documents check_names.py (--fix, --fix-ids, status codes table)
- Adds Migrating Existing Mods section for servers with pre-existing mods
- Updates Python requirement from 3.11 to 3.9
- Updates test count from 71 to 85
- Updates check_deps example output to show 3.9.2
- Adds check_names.py to folder structure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ID_COLLISION status (previously NOT_ON_SERVER) now has its own label
and shows the bad steam_id in the report output.
--fix-ids cross-references comparison.json to find the correct
publishedid for each colliding folder and rewrites it in-place in
meta.cpp using regex, preserving all other content.
New helpers: _lookup_detailed (returns server_name + local_sid tuple),
_write_steam_id (regex-replace publishedid in meta.cpp),
_build_comparison_id_map (builds normalized_name -> steam_id map
from comparison.json).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a server folder's meta.cpp publishedid appears in a local folder
that has a completely different name, the steam_id lookup was returning
a wrong MISMATCH. Added a two-pass classification: any proposed rename
target that is already an exact OK match for another folder is
reclassified as NOT_ON_SERVER (steam_id collision) instead of MISMATCH.
_resolve_status moved to module level and takes ok_disk_names as a
parameter so it can be unit-tested independently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Compares @Mod folder names in downloads/ against server canonical
names. Uses meta.cpp steam_id as primary lookup (most reliable),
falls back to normalized name. Reports OK / MISMATCH / NOT_ON_SERVER
per folder.
--fix mode renames mismatched folders and updates arma_dir junctions
in one pass so the full pipeline can run cleanly afterward.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parser.py and update_mods.py were missing
'from __future__ import annotations', causing a TypeError on
Python < 3.10 when the X | Y union syntax is evaluated at runtime.
All other modules already had the import.
Also lowers MIN_PYTHON from 3.11 to 3.9 -- the toolchain does not
use any Python 3.10/3.11-specific stdlib features beyond the union
annotation syntax which is now handled by the future import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
str | None union syntax requires Python 3.10+. Adding
from __future__ import annotations makes all annotations
lazy so the script can run on older Python and report the
version requirement instead of crashing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pipeline: parse HTML presets, compare modlists, download from Caddy
file server, create junctions/symlinks to Arma 3 Server directory.
Includes update/sync flows, missing-mod reporting, OS compat layer,
shared config, dep checker, comprehensive test suite (71 tests).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>