Isolate storage and internal processing of retention settings from UI language #52

Closed
opened 2026-07-14 05:20:03 -07:00 by serrebi · 2 comments
Owner

Currently, the retention-related settings in BlindRSS are tightly coupled with their UI display labels. This causes the configuration to break when the user changes the application’s language.

Affected settings

  1. Article Retention
    • UI path: Settings > Feeds & Articles > Article Retention (combobox)
    • Config key: article_retention
  2. Retention Policy
    • UI path: Settings > Downloads > Retention Policy (combobox)
    • Config key: download_retention

Current behavior

  • The combobox options are defined in the retention_opts array in gui/dialogs.py.
  • These option labels are not wrapped in gettext() calls, so they are not localizable.
  • The selected value is stored in the config file as the exact label string shown in the UI.
  • Internal logic (e.g., _perform_retention_cleanup(), _retention_seconds() in gui/mainframe.py) directly processes these label strings.

The problem that prevents these strings from being localized is described below.

Problem

If a user:

  1. Sets a retention option while using Language A,
  2. Switches the UI to Language B,

then the stored config values (which are label strings in Language A) will no longer match the new localized labels in Language B. This leads to:

  • Incorrect or unrecognized retention settings.
  • Potential fallback to defaults or runtime errors.
  • Broken retention cleanup logic.

This violates the best practice of decoupling persistent configuration from UI presentation.

Required solution

Refactor the retention settings so that:

  1. Internal representation uses stable, language-independent identifiers (e.g., 1_day, 1_year, 5_years, etc.).
  2. Config file stores these stable identifiers, not UI labels.
  3. UI layer maps these identifiers to localized display labels using gettext().
  4. All internal logic (including _perform_retention_cleanup(), _retention_seconds(), and any other places) operates on the stable identifiers, not on UI label strings.
  5. Migration: If possible, provide a one-time migration for existing config files so that old label-based values are converted to the new identifier-based format. If migration is too risky or complex, clearly document the need to reconfigure these settings after the update.

Scope

  • Update gui/dialogs.py to define retention_opts as a mapping from identifier → localized label.
  • Modify config handling to store/read identifiers.
  • Refactor gui/mainframe.py methods (_perform_retention_cleanup(), _retention_seconds()) to work with identifiers.
  • Check for any other places in the codebase that directly use these label strings and update them accordingly.

Acceptance criteria

  • Retention settings are stored in the config file using stable, language-independent identifiers.
  • UI displays localized labels for these settings based on the current language.
  • Changing the UI language does not break existing retention settings.
  • All internal logic uses the stable identifiers instead of UI label strings.

Additional notes

  • Pay special attention to how the current label strings are used in retention calculation and cleanup logic; ensure no hidden assumptions about their format remain.
  • Consider adding type hints or an enum for the retention identifiers to improve code clarity and prevent future regressions.
Currently, the retention-related settings in BlindRSS are tightly coupled with their UI display labels. This causes the configuration to break when the user changes the application’s language. #### Affected settings 1. **Article Retention** - UI path: `Settings > Feeds & Articles > Article Retention` (combobox) - Config key: `article_retention` 2. **Retention Policy** - UI path: `Settings > Downloads > Retention Policy` (combobox) - Config key: `download_retention` #### Current behavior - The combobox options are defined in the `retention_opts` array in `gui/dialogs.py`. - These option labels are **not** wrapped in `gettext()` calls, so they are not localizable. - The selected value is stored in the config file **as the exact label string** shown in the UI. - Internal logic (e.g., `_perform_retention_cleanup()`, `_retention_seconds()` in `gui/mainframe.py`) directly processes these label strings. The problem that prevents these strings from being localized is described below. #### Problem If a user: 1. Sets a retention option while using Language A, 2. Switches the UI to Language B, then the stored config values (which are label strings in Language A) will no longer match the new localized labels in Language B. This leads to: - Incorrect or unrecognized retention settings. - Potential fallback to defaults or runtime errors. - Broken retention cleanup logic. This violates the best practice of **decoupling persistent configuration from UI presentation**. #### Required solution Refactor the retention settings so that: 1. **Internal representation** uses stable, language-independent identifiers (e.g., `1_day`, `1_year`, `5_years`, etc.). 2. **Config file** stores these stable identifiers, not UI labels. 3. **UI layer** maps these identifiers to localized display labels using `gettext()`. 4. **All internal logic** (including `_perform_retention_cleanup()`, `_retention_seconds()`, and any other places) operates on the stable identifiers, not on UI label strings. 5. **Migration**: If possible, provide a one-time migration for existing config files so that old label-based values are converted to the new identifier-based format. If migration is too risky or complex, clearly document the need to reconfigure these settings after the update. #### Scope - Update `gui/dialogs.py` to define `retention_opts` as a mapping from identifier → localized label. - Modify config handling to store/read identifiers. - Refactor `gui/mainframe.py` methods (`_perform_retention_cleanup()`, `_retention_seconds()`) to work with identifiers. - Check for any other places in the codebase that directly use these label strings and update them accordingly. ### Acceptance criteria - [ ] Retention settings are stored in the config file using stable, language-independent identifiers. - [ ] UI displays localized labels for these settings based on the current language. - [ ] Changing the UI language does not break existing retention settings. - [ ] All internal logic uses the stable identifiers instead of UI label strings. ### Additional notes - Pay special attention to how the current label strings are used in retention calculation and cleanup logic; ensure no hidden assumptions about their format remain. - Consider adding type hints or an enum for the retention identifiers to improve code clarity and prevent future regressions. <!-- forgejo-github-sync: issue github=serrebidev/BlindRSS#63 -->
Author
Owner

Fixed in v1.99.5. Thanks for the thorough write-up — implemented essentially as you specified.

What changed:

  • New core/retention.py defines the stable identifiers (1_day5_years, unlimited) as the single source of truth, along with the day/second math and the identifier-to-localized-label mapping.
  • The config now stores identifiers only. A one-time migration in ConfigManager converts existing label-based values for both article_retention and download_retention, including the legacy 2 days / 3 months labels that the old cleanup code accepted but the UI never offered. Unknown or corrupt values fall back to unlimited — the safe direction, so a bad value can never delete more than the user asked for.
  • _perform_retention_cleanup() and _retention_seconds() now operate on identifiers through the shared module; the hardcoded label tables are gone. Legacy label strings remain accepted defensively anywhere they might still appear.
  • The comboboxes display gettext-localized labels and map selection index back to the identifier on save, so the display language can never leak into the config again. The labels are translated in all 15 languages in this release.
  • Regression tests: tests/test_retention_identifiers.py (normalization, migration, both value forms end to end).

Also in v1.99.5 — the bug you reported about new articles not appearing until the entire refresh finishes is fixed. It was a regression from v1.99.0: the list-churn work deferred every article-list reload until the whole refresh batch ended. The visible list now reloads on a slow throttle (about every 2.5 seconds) while a refresh is running, so articles stream in as each feed completes again — without going back to the per-feed reload churn that used to starve full-text extraction.

Fixed in [v1.99.5](https://github.com/serrebidev/BlindRSS/releases/tag/v1.99.5). Thanks for the thorough write-up — implemented essentially as you specified. **What changed:** - New `core/retention.py` defines the stable identifiers (`1_day` … `5_years`, `unlimited`) as the single source of truth, along with the day/second math and the identifier-to-localized-label mapping. - The config now stores identifiers only. A one-time migration in `ConfigManager` converts existing label-based values for both `article_retention` and `download_retention`, including the legacy `2 days` / `3 months` labels that the old cleanup code accepted but the UI never offered. Unknown or corrupt values fall back to `unlimited` — the safe direction, so a bad value can never delete more than the user asked for. - `_perform_retention_cleanup()` and `_retention_seconds()` now operate on identifiers through the shared module; the hardcoded label tables are gone. Legacy label strings remain accepted defensively anywhere they might still appear. - The comboboxes display gettext-localized labels and map selection index back to the identifier on save, so the display language can never leak into the config again. The labels are translated in all 15 languages in this release. - Regression tests: `tests/test_retention_identifiers.py` (normalization, migration, both value forms end to end). **Also in v1.99.5** — the bug you reported about new articles not appearing until the entire refresh finishes is fixed. It was a regression from v1.99.0: the list-churn work deferred every article-list reload until the whole refresh batch ended. The visible list now reloads on a slow throttle (about every 2.5 seconds) while a refresh is running, so articles stream in as each feed completes again — without going back to the per-feed reload churn that used to starve full-text extraction. <!-- forgejo-github-sync: comment github=serrebidev/BlindRSS#63/4971977869 -->
Author
Owner

Thanks! We need to do the same for the "Uncategorized" system folder. It seems like localizing its name might break something. Please take a look at the current implementation.

Thanks! We need to do the same for the "Uncategorized" system folder. It seems like localizing its name might break something. Please take a look at the current implementation. <!-- forgejo-github-sync: comment github=serrebidev/BlindRSS#63/4974063560 -->
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
serrebi/BlindRSS#52
No description provided.