TL;DR
- Quota recovery & SmartProxy (509) dialog and its Edit-menu entry are now translatable. 8.38 shipped them as hard-coded English literals — because the dialog is hand-built (not a NetBeans
.form), the boot-timetranslateLabels()sweep had nothing to localise, so every non-English locale saw English in the menu and inside the dialog. Every label, tooltip, button, and proxy-test output line now routes throughI18n.tr("ui.quota.*")(and the menu through"ui.menu.quota_recovery"), with translations added to everymessages*.propertiesbundle (de, es, hu, it, tr, vi, zh). - Everything that shipped in 8.38 is included (carried over below). 8.39 is 8.38 + the i18n patch above; there are no behaviour changes outside the dialog.
New in 8.39
Quota recovery dialog: untranslated strings closed
Reported in #752 (post-release follow-up). QuotaRecoverySettingsDialog is the only major dialog in the project that's built by hand (29 strings inside it: panel titles, the auto-resume checkbox + tooltip, the two spinner labels + tooltips, the diagnostics row, two buttons + tooltip, Save / Cancel, the help text, and the live proxy-test output messages). Because there's no NetBeans .form for it, the historical translateLabels(this) sweep that the existing dialogs rely on had nothing to do — every literal rendered in English regardless of UI locale. Same gap on the Edit-menu entry that opens it (MainPanel.java:454).
Fix: route every UI string through I18n.tr("ui.quota.*") (and the menu through "ui.menu.quota_recovery"), add the 29 keys to every src/main/resources/i18n/messages*.properties bundle, and pre-pad the per-row test output address column to 32 chars in code so the alignment survives translation (key body only carries the trailing label and placeholders).
Going forward: an auto-memory rule was added so any new UI string in code or .form is required to land in every bundle, not as an English literal.
Highlights (carried over from 8.38)
Issue #752 — Quota recovery dialog Save/Cancel buttons hidden
QuotaRecoverySettingsDialog.initComponents() added the footer (Save / Cancel) to BorderLayout.SOUTH and the help label at the bottom to BorderLayout.PAGE_END. In Java's BorderLayout the relative PAGE_END constraint takes precedence over the absolute SOUTH constraint, so the help label silently consumed the slot and the Save row never rendered. Users saw the spinner values reset on close and had no way to persist auto_resume_ip_change, quota_stall_timeout or smart_proxy_509_recheck_window — which is also the root cause of the "settings not reflected in SmartProxy at runtime" complaint in the same report (saveToDB never ran, so the live refreshSmartProxySettings() it normally triggers also never ran). Nest the footer and the help label in a single SOUTH-positioned container so both render.
Issue #752 — MEGA -17 (EOVERQUOTA) now auto-retries with a 60 s countdown
When MEGA returned -17 on getMegaFileMetadata / getMegaFileDownloadUrl the download was marked fatal without retry. -17 means "this IP / account has hit the bandwidth quota"; MEGA's window typically clears on its own (anonymous links) or via a VPN / IP change, which the rest of the recovery machinery already supports. Add -17 to FATAL_API_ERROR_CODES_WITH_RETRY so the cleanup path arms the auto-restart. Use a 60 s countdown for -17 specifically — the historical 3 s pace is right for -4 (ERATELIMIT, clears in seconds) but wrong for -17 (clears in tens of minutes), where a 3 s retry would just hammer the API with no chance of the quota actually resetting. The last fatal API error code is now tracked on the Download instance so the cleanup path can pick the appropriate countdown without inverting the existing call sites.
Issue #752 — MegaAPI.RAW_REQUEST SmartProxy retry was non-defensive
The HTTP 509 → SmartProxy retry path in MegaAPI.RAW_REQUEST did:
String[] smart_proxy = proxy_manager.getProxy(excluded_proxy_list);
current_smart_proxy = smart_proxy[0]; // NPE
...
Integer.parseInt(proxy_info[1]) // NumberFormatExceptiongetProxy() returns null when every proxy is banned / excluded and the refresh-retry loop is exhausted — exactly during a 509 storm, which is when SmartProxy is supposed to save us. A garbage proxy entry (custom list with a typo) tripped Integer.parseInt the same way. Neither exception is caught by RAW_REQUEST's try block (only IOException / SSLException are), so both would propagate out and kill the /cs request — and with it any pending getMegaFileDownloadUrl() or login flow that was supposed to be recovered through SmartProxy. Apply the same defensive pattern that ChunkDownloader has used since #751 / 207e722: on null getProxy() result log a WARNING and fall back to direct for this retry; on malformed entry block it and fall back to direct so getProxy() avoids it next round.
Issue #752 — SmartProxy ban_time / proxy_timeout clamped
SmartMegaProxyManager.refreshSmartProxySettings() now clamps smartproxy_ban_time to [10, 3600] s and smartproxy_timeout to [3, 120] s, with a WARNING log when clamping kicks in. Below 3 s of connect timeout most real-world public proxies cannot complete a TCP+TLS handshake (so every attempt times out and the worker burns through the list without ever connecting); below 10 s of ban a banned proxy is unblocked before the worker that banned it has finished retrying somewhere else, so the pool churns the same bad entries. The clamp protects the recovery path from these configurations regardless of how they got into the settings DB.
i18n migration to ResourceBundle / .properties
The custom Translator HashMap subsystem (a large in-memory Map<String,Map<String,String>> populated at boot) is replaced by Java's standard java.util.ResourceBundle machinery, backed by src/main/resources/i18n/messages*.properties bundles (one per language: en, es, it, de, hu, tr, zh, vi). Key benefits: standard tooling for translators, no boot-time parsing cost, parametric placeholders via MessageFormat, fallback to the root (English) bundle when a translation key is missing. A small I18n.LEGACY_CODE_TO_LOCALE shim maps MegaBasterd's historical two-letter codes (EN/ES/GE/TU/CH/VI/...) to the Locale objects ResourceBundle expects, and a LabelTranslatorSingleton.translate(englishLiteral) shim keeps the existing call sites (which pass the English literal as the key) working until each is migrated to the new I18n.tr("namespaced.key", args...) API. No behavioural change for users. Contributors: see issue #397 for the translation thread.
-Dmb.debug.force509=N JVM property
Reproducing the "free quota already exhausted before launch and SmartProxy never engages" report in #752 required forcing real 509 responses from MEGA, which is hard to set up reliably and burns actual quota when you do. New JVM property intercepts ChunkDownloader responses right after getResponseCode():
| Property | Effect |
|---|---|
-Dmb.debug.force509=N (N > 0)
| First N successful direct responses become synthetic 509s |
-Dmb.debug.force509=-1
| Every direct response forever |
| (unset / 0) | Disabled (default) |
Only direct connections are intercepted; SmartProxy-routed traffic flows through unaltered, so the recovery path (worker enters 509 backoff → SmartProxy engages → proxied chunk succeeds) can be exercised end-to-end without spending real bandwidth. A WARNING is logged on JVM startup when the toggle is active and again when the counter is exhausted, so the simulator can never silently leak into production.
Notes
- This release is settings-compatible with 8.25 – 8.38 DBs.
- Translation files now live in
src/main/resources/i18n/messages_<lang>.properties. Pull requests adding / improving translations should target those files; the legacy*.langHashMap inputs have been removed. - Closes #752 (the original cluster + the post-release i18n follow-up).