Skip to content

feat: Implement localization support and update application version#9

Merged
devohmycode merged 1 commit intomainfrom
0.1.2
Mar 19, 2026
Merged

feat: Implement localization support and update application version#9
devohmycode merged 1 commit intomainfrom
0.1.2

Conversation

@devohmycode
Copy link
Owner

@devohmycode devohmycode commented Mar 19, 2026

  • Added a simple i18n system for language translations, allowing dynamic language switching.
  • Updated MainWindow and LibraryWindow to utilize localization for UI text.
  • Enhanced SettingsWindow to support language selection and display localized text.
  • Updated application version to 0.1.2 in installer.iss.
  • Improved README with instructions for adding new languages.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multi-language support (English and French) with language selection in Settings and persistent user preferences.
    • Implemented real-time library folder monitoring that automatically detects and synchronizes track changes (file additions, deletions, and modifications) without manual refresh.
    • Localized UI text including labels, headers, messages, dialogs, and status indicators across the entire application interface.
  • Chores

    • Version updated to 0.1.2.

- Added a simple i18n system for language translations, allowing dynamic language switching.
- Updated MainWindow and LibraryWindow to utilize localization for UI text.
- Enhanced SettingsWindow to support language selection and display localized text.
- Updated application version to 0.1.2 in installer.iss.
- Improved README with instructions for adding new languages.
@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 470dda3a-3d37-4b7a-8395-69a443136cec

📥 Commits

Reviewing files that changed from the base of the PR and between 6165438 and 3f7696e.

📒 Files selected for processing (13)
  • Audiomatic/App.xaml.cs
  • Audiomatic/LibraryWindow.xaml
  • Audiomatic/LibraryWindow.xaml.cs
  • Audiomatic/MainWindow.xaml
  • Audiomatic/MainWindow.xaml.cs
  • Audiomatic/Services/LibraryManager.cs
  • Audiomatic/Services/LibraryWatcher.cs
  • Audiomatic/SettingsManager.cs
  • Audiomatic/SettingsWindow.xaml
  • Audiomatic/SettingsWindow.xaml.cs
  • Audiomatic/Strings.cs
  • README.md
  • installer.iss

📝 Walkthrough

Walkthrough

This PR introduces internationalization (i18n) infrastructure with a translation dictionary system and language persistence, refactors UI elements across multiple windows to support dynamic localization via new ApplyLocalization() methods, adds real-time library folder monitoring via file system watchers that automatically sync track additions/deletions, implements dynamic navigation tab overflow handling in the main window, and updates the version to 0.1.2.

Changes

Cohort / File(s) Summary
Internationalization Core
Audiomatic/Strings.cs, Audiomatic/SettingsManager.cs, Audiomatic/App.xaml.cs
New Strings class with translation dictionary (T() methods) for English/French; SettingsManager adds LoadLanguage()/SaveLanguage() methods; default language changed to "en"; App.xaml.cs loads language at startup before creating MainWindow.
Library File System Monitoring
Audiomatic/Services/LibraryWatcher.cs, Audiomatic/Services/LibraryManager.cs
New LibraryWatcher class monitors configured library folders, debounces file system events (600ms), and syncs track additions/deletions via LibraryManager.AddOrUpdateFileAsync() and RemoveTrackByPath() methods; raises LibraryChanged event on successful operations.
MainWindow Localization & Navigation
Audiomatic/MainWindow.xaml, Audiomatic/MainWindow.xaml.cs
Added x:Name to 7 UI elements (back buttons, headers, labels); implemented ApplyLocalization() method to dynamically apply 100+ localized strings; added _overflowedTabIndices logic to hide nav tabs when space-constrained, populating overflow to "More" menu; integrated _libraryWatcher to auto-reload tracks on file changes; replaced hardcoded UI text with Strings.T() calls throughout.
Library & Settings Window Localization
Audiomatic/LibraryWindow.xaml, Audiomatic/LibraryWindow.xaml.cs, Audiomatic/SettingsWindow.xaml, Audiomatic/SettingsWindow.xaml.cs
Added x:Name identifiers to text elements; new ApplyLocalization() methods in both windows apply translated labels, headers, descriptions, and status messages (e.g., folder scan progress, completion); updated LibraryWindow sort headers to use localized keys.
Documentation & Version
README.md, installer.iss
Added "Development" section documenting how to add new languages to the i18n system; bumped installer version from 0.1.0 to 0.1.2.

Sequence Diagram

sequenceDiagram
    participant FS as File System
    participant FW as FileSystemWatcher
    participant LW as LibraryWatcher
    participant LM as LibraryManager
    participant UI as MainWindow

    FS->>FW: File Created/Changed/Deleted
    FW->>LW: OnChanged event
    LW->>LW: Queue to pending map<br/>(debounce 600ms)
    activate LW
    Note over LW: Debounce timer fires
    LW->>LW: Drain pending changes
    
    rect rgba(100, 150, 200, 0.5)
        Note over LW: For each file
        alt Deletion
            LW->>LM: RemoveTrackByPath(path)
            LM-->>LW: bool success
        else Creation/Modification
            LW->>LM: AddOrUpdateFileAsync(filePath)
            LM->>LM: Validate, ReadMetadata
            LM->>LM: InsertTrack (transaction)
            LM-->>LW: bool success
        end
    end
    
    deactivate LW
    
    alt Any operation succeeded
        LW->>UI: LibraryChanged event
        UI->>UI: LoadTracks() on UI thread
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hooray for translations true and tongue,
Watch those folders, track by track so young,
Navigation flows when tabs must hide,
From "en" to "fr", we localize with pride! ✨📁

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 0.1.2
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@devohmycode devohmycode merged commit 83bc0da into main Mar 19, 2026
1 check was pending
@qodo-code-review
Copy link

Review Summary by Qodo

Add localization support, real-time library watching, and responsive navigation

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Implemented comprehensive i18n localization system with English and French translations
• Added real-time folder monitoring via LibraryWatcher for automatic library updates
• Enhanced navigation UI with dynamic tab overflow handling and responsive layout
• Integrated language selection in settings with persistent storage and UI refresh
• Updated application version to 0.1.2 and improved documentation
Diagram
flowchart LR
  A["Strings.cs<br/>i18n System"] -->|Translate| B["MainWindow<br/>UI Text"]
  A -->|Translate| C["SettingsWindow<br/>UI Text"]
  A -->|Translate| D["LibraryWindow<br/>UI Text"]
  E["LibraryWatcher<br/>FileSystem Monitor"] -->|Detect Changes| F["LibraryManager<br/>Add/Remove Tracks"]
  F -->|Notify| B
  G["SettingsManager<br/>Language Persistence"] -->|Load/Save| A
  H["NavOverflow Logic<br/>Responsive Tabs"] -->|Adapt| B
Loading

Grey Divider

File Changes

1. Audiomatic/Strings.cs ✨ Enhancement +246/-0

New i18n translation system with 200+ keys

Audiomatic/Strings.cs


2. Audiomatic/MainWindow.xaml.cs ✨ Enhancement +370/-150

Localize all UI text and add nav overflow handling

Audiomatic/MainWindow.xaml.cs


3. Audiomatic/Services/LibraryWatcher.cs ✨ Enhancement +192/-0

Real-time file system monitoring for library updates

Audiomatic/Services/LibraryWatcher.cs


View more (10)
4. Audiomatic/Services/LibraryManager.cs ✨ Enhancement +38/-0

Add single-file add/remove operations for watcher

Audiomatic/Services/LibraryManager.cs


5. Audiomatic/SettingsWindow.xaml.cs ✨ Enhancement +44/-11

Apply localization to settings UI elements

Audiomatic/SettingsWindow.xaml.cs


6. Audiomatic/SettingsWindow.xaml ✨ Enhancement +10/-10

Add x:Name attributes for localization binding

Audiomatic/SettingsWindow.xaml


7. Audiomatic/LibraryWindow.xaml.cs ✨ Enhancement +9/-2

Apply localization to library window text

Audiomatic/LibraryWindow.xaml.cs


8. Audiomatic/LibraryWindow.xaml ✨ Enhancement +2/-2

Add x:Name attributes for localization binding

Audiomatic/LibraryWindow.xaml


9. Audiomatic/MainWindow.xaml ✨ Enhancement +7/-7

Add x:Name attributes for localization binding

Audiomatic/MainWindow.xaml


10. Audiomatic/SettingsManager.cs ✨ Enhancement +10/-1

Add language persistence and default to English

Audiomatic/SettingsManager.cs


11. Audiomatic/App.xaml.cs ✨ Enhancement +1/-0

Initialize language on app startup

Audiomatic/App.xaml.cs


12. installer.iss ⚙️ Configuration changes +1/-1

Update application version to 0.1.2

installer.iss


13. README.md 📝 Documentation +26/-0

Add language development guide and instructions

README.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 19, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Watcher deletes playlist entries 🐞 Bug ✓ Correctness
Description
LibraryManager.AddOrUpdateFileAsync() updates tracks via InsertTrack() which uses `INSERT OR
REPLACE, causing SQLite to delete and recreate rows when tracks.path` conflicts. Because
playlist_tracks and favorites reference tracks(id) with ON DELETE CASCADE, a normal file
change processed by LibraryWatcher can silently remove playlist membership and favorites for the
affected track.
Code

Audiomatic/Services/LibraryManager.cs[R670-692]

+    public static async Task<bool> AddOrUpdateFileAsync(string filePath)
+    {
+        var ext = Path.GetExtension(filePath).ToLowerInvariant();
+        if (!AudioExtensions.Contains(ext)) return false;
+        if (!File.Exists(filePath)) return false;
+
+        var folders = GetFolders();
+        var folder = folders
+            .Where(f => f.Enabled && filePath.StartsWith(f.Path, StringComparison.OrdinalIgnoreCase))
+            .OrderByDescending(f => f.Path.Length)
+            .FirstOrDefault();
+
+        if (folder == null) return false;
+
+        var track = await Task.Run(() => ReadMetadata(filePath, folder.Id));
+
+        using var conn = new SqliteConnection(ConnectionString);
+        await conn.OpenAsync();
+        EnablePragmas(conn);
+        using var transaction = conn.BeginTransaction();
+        InsertTrack(conn, track, transaction);
+        transaction.Commit();
+        return true;
Evidence
The DB schema makes tracks.path unique and enforces cascading deletes from tracks(id) to
playlist/favorites tables; InsertTrack() uses INSERT OR REPLACE, which deletes the conflicting
row before inserting a new one. The newly-added watcher update path calls InsertTrack() for
changed/created events, so file modifications can trigger the cascade.

Audiomatic/Services/LibraryManager.cs[42-85]
Audiomatic/Services/LibraryManager.cs[269-292]
Audiomatic/Services/LibraryManager.cs[670-692]
Audiomatic/Services/LibraryWatcher.cs[110-151]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`AddOrUpdateFileAsync()` currently calls `InsertTrack()` which executes `INSERT OR REPLACE INTO tracks ...`. In SQLite, `OR REPLACE` deletes the existing row on a UNIQUE conflict (here: `tracks.path`), which triggers `ON DELETE CASCADE` on `playlist_tracks(track_id)` and `favorites(track_id)`, silently removing user data.

### Issue Context
- The watcher pathway will run frequently (Created/Changed events), making this data loss much more likely than manual rescans.

### Fix approach
- Replace the `INSERT OR REPLACE` behavior *for watcher-driven updates* with an UPSERT that preserves the existing rowid/`id`.
- Prefer SQLite `ON CONFLICT(path) DO UPDATE SET ...` so the existing row is updated in-place.
- Preserve `created_at` on updates (don’t overwrite it every time a file changes).

### Fix Focus Areas
- Audiomatic/Services/LibraryManager.cs[269-292]
- Audiomatic/Services/LibraryManager.cs[670-692]
- Audiomatic/Services/LibraryWatcher.cs[110-151]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Rename drops playlist links 🐞 Bug ✓ Correctness
Description
LibraryWatcher.OnRenamed converts a rename into a delete of the old path plus a create of the new
path. This removes the old tracks row (cascading to playlist_tracks/favorites) and then
inserts a new track row, so renaming/moving a file within a watched folder can silently remove it
from playlists and favorites.
Code

Audiomatic/Services/LibraryWatcher.cs[R81-88]

+    private void OnRenamed(object sender, RenamedEventArgs e)
+    {
+        QueueChange(e.OldFullPath, WatcherChangeType.Deleted);
+
+        var ext = Path.GetExtension(e.FullPath).ToLowerInvariant();
+        if (LibraryManager.AudioExtensions.Contains(ext))
+            QueueChange(e.FullPath, WatcherChangeType.Created);
+    }
Evidence
The watcher explicitly queues Deleted for OldFullPath and Created for the new path. Processing
a Deleted event calls LibraryManager.RemoveTrackByPath() which deletes from tracks by path;
because playlist/favorites have ON DELETE CASCADE to tracks(id), associations are removed. The
subsequent create path re-adds the track as a new row (and with INSERT OR REPLACE semantics today
may also recreate ids).

Audiomatic/Services/LibraryWatcher.cs[81-88]
Audiomatic/Services/LibraryWatcher.cs[125-141]
Audiomatic/Services/LibraryManager.cs[659-667]
Audiomatic/Services/LibraryManager.cs[72-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
File renames are currently treated as `DELETE oldPath` + `INSERT newPath`, which drops playlist/favorites relationships because they’re keyed by `track_id`.

### Issue Context
- `OnRenamed()` queues `Deleted` for `OldFullPath` and `Created` for the new file.
- `ProcessChanges()` maps `Deleted` to `RemoveTrackByPath()`.

### Fix approach
- Add a DB-level rename operation that preserves `tracks.id`, e.g. `UPDATE tracks SET path=@newPath, last_modified=@... WHERE path=@oldPath`.
- In `OnRenamed()`, queue a rename operation (or store a mapping) rather than delete+create.
- After path update, optionally refresh metadata for the new path (but using an in-place UPSERT/update that preserves ids).

### Fix Focus Areas
- Audiomatic/Services/LibraryWatcher.cs[81-88]
- Audiomatic/Services/LibraryWatcher.cs[110-151]
- Audiomatic/Services/LibraryManager.cs[657-693]
- Audiomatic/Services/LibraryManager.cs[42-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Localization resets track count 🐞 Bug ✓ Correctness
Description
MainWindow.ApplyLocalization() overwrites TrackCountText with the localized string for "0
tracks" regardless of the current view. In view modes where ApplyFilterAndSort() returns early
(Visualizer/Albums/Artists), the counter never gets recomputed, leaving an incorrect count after
language switching.
Code

Audiomatic/MainWindow.xaml.cs[R5112-5126]

+        // Bottom bar
+        ChooseFolderText.Text = Strings.T("Choose Folder");
+        TrackCountText.Text = Strings.T("0 tracks");
+
+        // Now playing defaults (only if no track is active)
+        if (_player.CurrentTrack == null && !_isRadioPlaying)
+        {
+            TrackTitle.Text = Strings.T("No track");
+            MiniTrackText.Text = Strings.T("No track");
+        }
+
+        // Rebuild current view to pick up new strings
+        UpdateNavigation();
+        ApplyFilterAndSort();
+    }
Evidence
ApplyLocalization() sets TrackCountText.Text = Strings.T("0 tracks") and then calls
ApplyFilterAndSort(). But ApplyFilterAndSort() exits immediately for ViewMode.Visualizer,
ViewMode.Albums, and ViewMode.Artists, so it never updates TrackCountText in those modes after
the forced reset.

Audiomatic/MainWindow.xaml.cs[5066-5126]
Audiomatic/MainWindow.xaml.cs[328-343]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
When the user switches language, `ApplyLocalization()` hard-resets the track count to `&quot;0 tracks&quot;`. For some view modes, the subsequent recompute path is skipped, so the UI shows an incorrect count.

### Issue Context
- `ApplyLocalization()` currently always assigns `TrackCountText.Text = Strings.T(&quot;0 tracks&quot;)`.
- `ApplyFilterAndSort()` returns early in Visualizer/Albums/Artists modes without updating `TrackCountText`.

### Fix approach
- Remove the unconditional `TrackCountText` reset.
- After applying localization, recompute the bottom-bar count based on the active view mode:
 - Library/PlaylistDetail/AlbumDetail/ArtistDetail: use existing `ApplyFilterAndSort()` logic.
 - Albums/Artists: ensure the grid builders (e.g., `BuildAlbumsGrid()` / `BuildArtistsGrid()`) are invoked or at least re-emit the localized count string.
 - Visualizer: keep existing count (don’t clobber it).

### Fix Focus Areas
- Audiomatic/MainWindow.xaml.cs[5066-5126]
- Audiomatic/MainWindow.xaml.cs[328-391]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +670 to +692
public static async Task<bool> AddOrUpdateFileAsync(string filePath)
{
var ext = Path.GetExtension(filePath).ToLowerInvariant();
if (!AudioExtensions.Contains(ext)) return false;
if (!File.Exists(filePath)) return false;

var folders = GetFolders();
var folder = folders
.Where(f => f.Enabled && filePath.StartsWith(f.Path, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(f => f.Path.Length)
.FirstOrDefault();

if (folder == null) return false;

var track = await Task.Run(() => ReadMetadata(filePath, folder.Id));

using var conn = new SqliteConnection(ConnectionString);
await conn.OpenAsync();
EnablePragmas(conn);
using var transaction = conn.BeginTransaction();
InsertTrack(conn, track, transaction);
transaction.Commit();
return true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Watcher deletes playlist entries 🐞 Bug ✓ Correctness

LibraryManager.AddOrUpdateFileAsync() updates tracks via InsertTrack() which uses `INSERT OR
REPLACE, causing SQLite to delete and recreate rows when tracks.path` conflicts. Because
playlist_tracks and favorites reference tracks(id) with ON DELETE CASCADE, a normal file
change processed by LibraryWatcher can silently remove playlist membership and favorites for the
affected track.
Agent Prompt
### Issue description
`AddOrUpdateFileAsync()` currently calls `InsertTrack()` which executes `INSERT OR REPLACE INTO tracks ...`. In SQLite, `OR REPLACE` deletes the existing row on a UNIQUE conflict (here: `tracks.path`), which triggers `ON DELETE CASCADE` on `playlist_tracks(track_id)` and `favorites(track_id)`, silently removing user data.

### Issue Context
- The watcher pathway will run frequently (Created/Changed events), making this data loss much more likely than manual rescans.

### Fix approach
- Replace the `INSERT OR REPLACE` behavior *for watcher-driven updates* with an UPSERT that preserves the existing rowid/`id`.
- Prefer SQLite `ON CONFLICT(path) DO UPDATE SET ...` so the existing row is updated in-place.
- Preserve `created_at` on updates (don’t overwrite it every time a file changes).

### Fix Focus Areas
- Audiomatic/Services/LibraryManager.cs[269-292]
- Audiomatic/Services/LibraryManager.cs[670-692]
- Audiomatic/Services/LibraryWatcher.cs[110-151]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +81 to +88
private void OnRenamed(object sender, RenamedEventArgs e)
{
QueueChange(e.OldFullPath, WatcherChangeType.Deleted);

var ext = Path.GetExtension(e.FullPath).ToLowerInvariant();
if (LibraryManager.AudioExtensions.Contains(ext))
QueueChange(e.FullPath, WatcherChangeType.Created);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Rename drops playlist links 🐞 Bug ✓ Correctness

LibraryWatcher.OnRenamed converts a rename into a delete of the old path plus a create of the new
path. This removes the old tracks row (cascading to playlist_tracks/favorites) and then
inserts a new track row, so renaming/moving a file within a watched folder can silently remove it
from playlists and favorites.
Agent Prompt
### Issue description
File renames are currently treated as `DELETE oldPath` + `INSERT newPath`, which drops playlist/favorites relationships because they’re keyed by `track_id`.

### Issue Context
- `OnRenamed()` queues `Deleted` for `OldFullPath` and `Created` for the new file.
- `ProcessChanges()` maps `Deleted` to `RemoveTrackByPath()`.

### Fix approach
- Add a DB-level rename operation that preserves `tracks.id`, e.g. `UPDATE tracks SET path=@newPath, last_modified=@... WHERE path=@oldPath`.
- In `OnRenamed()`, queue a rename operation (or store a mapping) rather than delete+create.
- After path update, optionally refresh metadata for the new path (but using an in-place UPSERT/update that preserves ids).

### Fix Focus Areas
- Audiomatic/Services/LibraryWatcher.cs[81-88]
- Audiomatic/Services/LibraryWatcher.cs[110-151]
- Audiomatic/Services/LibraryManager.cs[657-693]
- Audiomatic/Services/LibraryManager.cs[42-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai bot mentioned this pull request Mar 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant