Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ separately in [CHANGELOG-DEPS.md](CHANGELOG-DEPS.md).

## Unreleased

- **Choose which sidebar sections you see.** Settings → Appearance → Sidebar
can hide Favorites and the Browse folder tree independently, without removing
saved favorites or restarting Ferail.

- **The interface is available in Italian.** Select Italiano under Language
in Appearance settings.

- **Going up to a parent folder selects the folder you came from and scrolls
it into view, whichever way you go up.** Backspace and Back already did
this; clicking a breadcrumb segment, a folder in the sidebar tree, or typing
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,9 @@ the same change:
`msgid!` form described in
[Localization](docs/features/LOCALIZATION.md#for-developers-writing-strings).
2. Regenerate `locales/en.json` with the extraction test.
3. Add or update the matching entries in both bundled packs,
`locales/de.json` and `locales/fr.json`. Preserve every placeholder and
3. Add or update the matching entries in all bundled packs:
`locales/de.json`, `locales/fr.json`, `locales/it.json`, and `locales/pl.json`.
Preserve every placeholder and
plural shape; do not ship newly added text as an English fallback.
4. Run the i18n extraction and bundled-pack tests before finishing.

Expand Down Expand Up @@ -279,7 +280,7 @@ Before finishing code changes:
- If the change touches icons, update
[docs/features/ICONS.md](docs/features/ICONS.md) (see [Icons](#icons)).
- If the change adds or rewrites user-visible text, regenerate the English
catalog, translate the new entries in both bundled language packs, and run
catalog, translate the new entries in all bundled language packs, and run
the i18n tests (see [Localization](#localization)).
- If the change displays a count of anything, group it (see
[Counts](#counts)).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ The ❌ rows are real gaps, not modesty.
| **Built-in text and image editing** | ✅ editor + redact/annotate | ⚠️ Quick Look markup | ❌ | ⚠️ opens another app |
| **Capture-safe screenshot mode** | ✅ Private Mode | ❌ | ❌ | ❌ |
| **Global / indexed search** | ⚠️ Spotlight-backed, single query box | ✅ rich | ✅ indexed | ✅ Tracker / Baloo |
| **Localization** | ⚠️ four bundled languages, any other importable | ✅ dozens | ✅ dozens | ✅ dozens |
| **Localization** | ⚠️ five bundled languages, any other importable | ✅ dozens | ✅ dozens | ✅ dozens |
| **Accessibility / maturity** | ❌ young, single-dev | ✅ decades | ✅ decades | ✅ mature |

✅ first-class · ⚠️ partial / varies · ❌ absent
Expand Down
1 change: 1 addition & 0 deletions crates/ferail-core/src/i18n/pack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub const EN_JSON: &str = include_str!("../../../../locales/en.json");
/// (in the config dir) takes precedence, so anyone can fix a string
/// locally before contributing it back.
pub const BUNDLED: &[(&str, &str)] = &[
("it", include_str!("../../../../locales/it.json")),
("fr", include_str!("../../../../locales/fr.json")),
("de", include_str!("../../../../locales/de.json")),
// Contributed by Bohun (github.com/bohunamiga).
Expand Down
16 changes: 15 additions & 1 deletion crates/ferail-gpui/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ pub struct AppState {
pub sidebar_section_order: Option<String>,
/// Stable comma-separated IDs of collapsed sidebar sections.
pub sidebar_collapsed_sections: Option<String>,
pub browse_enabled: Option<bool>,
pub favorites_enabled: Option<bool>,
/// Viewer slideshow auto-advance interval in seconds
/// (docs/features/VIEWER.md). Clamped at load to [1, 60].
pub viewer_slideshow_interval: Option<u64>,
Expand Down Expand Up @@ -525,6 +527,8 @@ fn load_from_disk() -> AppState {
"viewer_media_only" => {
out.viewer_media_only = parse_bool(val);
}
"browse_enabled" => out.browse_enabled = parse_bool(val),
"favorites_enabled" => out.favorites_enabled = parse_bool(val),
"recents_enabled" => {
out.recents_enabled = parse_bool(val);
}
Expand Down Expand Up @@ -723,6 +727,12 @@ fn serialize(state: &AppState) -> String {
if let Some(b) = state.viewer_media_only {
s.push_str(&format!("viewer_media_only={b}\n"));
}
if let Some(b) = state.browse_enabled {
s.push_str(&format!("browse_enabled={b}\n"));
}
if let Some(b) = state.favorites_enabled {
s.push_str(&format!("favorites_enabled={b}\n"));
}
if let Some(b) = state.recents_enabled {
s.push_str(&format!("recents_enabled={b}\n"));
}
Expand Down Expand Up @@ -847,13 +857,17 @@ mod tests {
let state = AppState {
sidebar_section_order: Some("volumes,locations,browse".into()),
sidebar_collapsed_sections: Some("locations,browse".into()),
browse_enabled: Some(false),
favorites_enabled: Some(false),
..AppState::default()
};
assert_eq!(
serialize(&state),
concat!(
"sidebar_section_order=volumes,locations,browse\n",
"sidebar_collapsed_sections=locations,browse\n"
"sidebar_collapsed_sections=locations,browse\n",
"browse_enabled=false\n",
"favorites_enabled=false\n"
)
);
}
Expand Down
36 changes: 36 additions & 0 deletions crates/ferail-gpui/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,20 @@ fn persist_exclude_favorites_from_tracking(value: bool) {
});
}

fn persist_browse_enabled(value: bool) {
app_state::save(&AppState {
browse_enabled: Some(value),
..app_state::load()
});
}

fn persist_favorites_enabled(value: bool) {
app_state::save(&AppState {
favorites_enabled: Some(value),
..app_state::load()
});
}

fn persist_recents_enabled(value: bool) {
let existing = app_state::load();
app_state::save(&AppState {
Expand Down Expand Up @@ -1458,6 +1472,28 @@ fn appearance_page(
SettingPage::new(tr!("Appearance"))
.icon(Icon::empty().path("icons/palette.svg"))
.group(language_group())
.group(
SettingGroup::new()
.title(tr!("Sidebar"))
.item(switch_setting(
tr!("Show Favorites"),
tr!("Show saved folder shortcuts in the sidebar."),
crate::sidebar_layout::favorites_enabled,
|value: bool, cx: &mut App| {
persist_favorites_enabled(value);
cx.set_global(crate::sidebar_layout::FavoritesEnabled(value));
},
))
.item(switch_setting(
tr!("Show Browse"),
tr!("Show the expandable Home folder tree in the sidebar."),
crate::sidebar_layout::browse_enabled,
|value: bool, cx: &mut App| {
persist_browse_enabled(value);
cx.set_global(crate::sidebar_layout::BrowseEnabled(value));
},
)),
)
.group(
SettingGroup::new().title(tr!("Theme")).item(
// Vertical layout so the three fixed-width tiles drop
Expand Down
6 changes: 6 additions & 0 deletions crates/ferail-gpui/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,12 @@ impl Shell {
cx.set_global(crate::ant_trail::ExcludeFavoritesFromTracking(
persisted.exclude_favorites_from_tracking.unwrap_or(true),
));
cx.set_global(crate::sidebar_layout::BrowseEnabled(
persisted.browse_enabled.unwrap_or(true),
));
cx.set_global(crate::sidebar_layout::FavoritesEnabled(
persisted.favorites_enabled.unwrap_or(true),
));
// Seed the Recents master switch. Default true (on).
cx.set_global(crate::recents_section::RecentsEnabled(
persisted.recents_enabled.unwrap_or(true),
Expand Down
42 changes: 25 additions & 17 deletions crates/ferail-gpui/src/shell/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4305,9 +4305,15 @@ impl Render for Shell {
let weak = cx.weak_entity();
let locations_rows = self.build_locations_rows(cx);
let platform_location_rows = self.build_platform_location_rows();
let favorites_section = self.build_user_favorites_section(weak.clone());
let favorites_section = crate::sidebar_layout::favorites_enabled(cx)
.then(|| self.build_user_favorites_section(weak.clone()));
let recents_section = self.build_recents_section(weak.clone(), cx);
let browse_rows = self.build_browse_rows(cx);
let browse_enabled = crate::sidebar_layout::browse_enabled(cx);
let browse_rows = if browse_enabled {
self.build_browse_rows(cx)
} else {
Vec::new()
};
let volumes_rows = self.build_volumes_rows(cx);
let has_volumes = !self.process.volumes.borrow().is_empty();

Expand Down Expand Up @@ -4365,19 +4371,21 @@ impl Render for Shell {
// sidebar to a 48-DIP icon strip.
use crate::sidebar_layout::SidebarSection;
let collapsed = |section| self.sidebar_layout.is_collapsed(section);
let mut sections: Vec<(SidebarSection, ShellSidebarItem)> = vec![
(
SidebarSection::Locations,
ShellSidebarItem::locations(crate::locations_section::LocationsSection::new(
tr!("Locations"),
locations_rows,
weak.clone(),
crate::tree::SIDEBAR_ICON_PX,
collapsed(SidebarSection::Locations),
)),
),
(SidebarSection::Favorites, favorites_section),
(
let mut sections: Vec<(SidebarSection, ShellSidebarItem)> = vec![(
SidebarSection::Locations,
ShellSidebarItem::locations(crate::locations_section::LocationsSection::new(
tr!("Locations"),
locations_rows,
weak.clone(),
crate::tree::SIDEBAR_ICON_PX,
collapsed(SidebarSection::Locations),
)),
)];
if let Some(favorites_section) = favorites_section {
sections.push((SidebarSection::Favorites, favorites_section));
}
if browse_enabled {
sections.push((
SidebarSection::Browse,
ShellSidebarItem::tree(TreeSection::new(
tr!("Browse"),
Expand All @@ -4388,8 +4396,8 @@ impl Render for Shell {
SidebarSection::Browse,
collapsed(SidebarSection::Browse),
)),
),
];
));
}
#[cfg(windows)]
sections.push((
SidebarSection::Windows,
Expand Down
17 changes: 17 additions & 0 deletions crates/ferail-gpui/src/sidebar_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@

use std::collections::HashSet;

#[derive(Clone, Copy)]
pub struct BrowseEnabled(pub bool);
impl gpui::Global for BrowseEnabled {}

pub fn browse_enabled(cx: &gpui::App) -> bool {
cx.try_global::<BrowseEnabled>().is_none_or(|value| value.0)
}

#[derive(Clone, Copy)]
pub struct FavoritesEnabled(pub bool);
impl gpui::Global for FavoritesEnabled {}

pub fn favorites_enabled(cx: &gpui::App) -> bool {
cx.try_global::<FavoritesEnabled>()
.is_none_or(|value| value.0)
}

/// Geometry for the icon-only sidebar. Values are effective DIPs after UI
/// zoom, while icon renderers still receive their unscaled rem-relative size.
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down
4 changes: 4 additions & 0 deletions docs/features/FAVORITES.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ A complete behavioral spec for the Favorites section of the sidebar. Written to

**Favorites** is a user-curated, ordered list of locations pinned to the sidebar for one-click access. A favorite is a *reference to a location*, a folder, a volume, a saved search, or a tag, not a copy of its contents.

Settings → Appearance → Sidebar controls the visibility of Favorites and the
Browse folder tree independently. Both are shown by default; hiding Favorites
keeps its saved entries. Changes apply to open windows and persist across restarts.

Favorites are distinct from:
- **Locations** (Home, Desktop, Documents, etc.), the OS-standard folders. These are *not* favorites; they're a separate, fixed section. (Note: this also resolves the duplicate-Downloads bug, Locations is the standard set, Favorites is the user's set, and they're visually separated.)
- **The file tree**: the expandable hierarchy. The tree shows structure; Favorites shows shortcuts.
Expand Down
5 changes: 3 additions & 2 deletions docs/features/LOCALIZATION.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Localization

Ferail's UI can be shown in any language for which a **language pack** exists.
Packs are plain JSON files; three (French, German, Polish) ship inside the app, and
Packs are plain JSON files; four (French, German, Italian, Polish) ship inside the app, and
anyone can create another one without touching the code, typically by
handing a generated template to a translator or to an AI assistant (Claude,
ChatGPT, a local model…) and importing the result. Ferail itself never calls
Expand Down Expand Up @@ -133,7 +133,8 @@ Rules:

- **New or changed user-visible text ships translated.** In the same change,
regenerate `locales/en.json` and add or update the corresponding entries in
every bundled pack (`locales/de.json`, `locales/fr.json`, `locales/pl.json`).
every bundled pack (`locales/de.json`, `locales/fr.json`, `locales/it.json`,
`locales/pl.json`).
Do not leave new strings to fall back to English.
- **Arguments must be string literals.** The extractor (see below) only sees
literals; a dynamic `tr!(x)` does not compile. For a `&'static str` that
Expand Down
5 changes: 5 additions & 0 deletions locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,8 +1071,10 @@
"Shortcut": "Verknüpfung",
"Show All": "Alle einblenden",
"Show Ant Trail": "Ant Trail anzeigen",
"Show Browse": "Ordnerbaum anzeigen",
"Show Contents": "Inhalt anzeigen",
"Show Desktop": "Schreibtisch anzeigen",
"Show Favorites": "Favoriten anzeigen",
"Show Hidden Files": "Versteckte Dateien anzeigen",
"Show Linux (WSL)": "Linux (WSL) anzeigen",
"Show Preview Pane": "Vorschaubereich anzeigen",
Expand All @@ -1088,13 +1090,16 @@
"Show largest-files panel": "Panel „Größte Dateien“ anzeigen",
"Show matching files only": "Nur passende Dateien anzeigen",
"Show only images, videos and audio": "Nur Bilder, Videos und Audio anzeigen",
"Show saved folder shortcuts in the sidebar.": "Gespeicherte Ordnerverknüpfungen in der Seitenleiste anzeigen.",
"Show the expandable Home folder tree in the sidebar.": "Den aufklappbaren Home-Ordnerbaum in der Seitenleiste anzeigen.",
"Show the full treemap and dim non-matches": "Gesamte Treemap anzeigen und andere Dateien abblenden",
"Showing info for the first {cap} of {total} items.": "Informationen für die ersten {cap} von {total} Objekten.",
"Showing {n} item in {app}": {
"one": "{n} Objekt wird im {app} angezeigt",
"other": "{n} Objekte werden im {app} angezeigt"
},
"Showing “{name}” in {app}": "„{name}“ wird im {app} angezeigt",
"Sidebar": "Seitenleiste",
"Sidecar files": "Begleitdateien",
"Similar image scan": "Suche nach ähnlichen Bildern",
"Similar image scan finished: no similar images found": "Suche nach ähnlichen Bildern abgeschlossen: Keine ähnlichen Bilder gefunden",
Expand Down
5 changes: 5 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,8 +1071,10 @@
"Shortcut": "Shortcut",
"Show All": "Show All",
"Show Ant Trail": "Show Ant Trail",
"Show Browse": "Show Browse",
"Show Contents": "Show Contents",
"Show Desktop": "Show Desktop",
"Show Favorites": "Show Favorites",
"Show Hidden Files": "Show Hidden Files",
"Show Linux (WSL)": "Show Linux (WSL)",
"Show Preview Pane": "Show Preview Pane",
Expand All @@ -1088,13 +1090,16 @@
"Show largest-files panel": "Show largest-files panel",
"Show matching files only": "Show matching files only",
"Show only images, videos and audio": "Show only images, videos and audio",
"Show saved folder shortcuts in the sidebar.": "Show saved folder shortcuts in the sidebar.",
"Show the expandable Home folder tree in the sidebar.": "Show the expandable Home folder tree in the sidebar.",
"Show the full treemap and dim non-matches": "Show the full treemap and dim non-matches",
"Showing info for the first {cap} of {total} items.": "Showing info for the first {cap} of {total} items.",
"Showing {n} item in {app}": {
"one": "Showing {n} item in {app}",
"other": "Showing {n} items in {app}"
},
"Showing “{name}” in {app}": "Showing “{name}” in {app}",
"Sidebar": "Sidebar",
"Sidecar files": "Sidecar files",
"Similar image scan": "Similar image scan",
"Similar image scan finished: no similar images found": "Similar image scan finished: no similar images found",
Expand Down
5 changes: 5 additions & 0 deletions locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,8 +1071,10 @@
"Shortcut": "Raccourci",
"Show All": "Afficher tout",
"Show Ant Trail": "Afficher l’Ant Trail",
"Show Browse": "Afficher Parcourir",
"Show Contents": "Afficher le contenu",
"Show Desktop": "Afficher le bureau",
"Show Favorites": "Afficher les favoris",
"Show Hidden Files": "Afficher les fichiers cachés",
"Show Linux (WSL)": "Afficher Linux (WSL)",
"Show Preview Pane": "Afficher le volet d'aperçu",
Expand All @@ -1088,13 +1090,16 @@
"Show largest-files panel": "Afficher le panneau des fichiers les plus volumineux",
"Show matching files only": "Afficher uniquement les fichiers correspondants",
"Show only images, videos and audio": "N’afficher que les images, vidéos et fichiers audio",
"Show saved folder shortcuts in the sidebar.": "Afficher les raccourcis de dossiers enregistrés dans la barre latérale.",
"Show the expandable Home folder tree in the sidebar.": "Afficher l’arborescence dépliable du dossier personnel dans la barre latérale.",
"Show the full treemap and dim non-matches": "Afficher tout le treemap et griser les autres fichiers",
"Showing info for the first {cap} of {total} items.": "Affichage des informations pour les {cap} premiers éléments sur {total}.",
"Showing {n} item in {app}": {
"one": "Affichage de l'élément {n} dans {app}",
"other": "Affichage des éléments {n} dans {app}"
},
"Showing “{name}” in {app}": "Affichage de « {name} » dans {app}",
"Sidebar": "Barre latérale",
"Sidecar files": "Fichiers annexes",
"Similar image scan": "Analyse des images similaires",
"Similar image scan finished: no similar images found": "Analyse des images similaires terminée : aucune image similaire trouvée",
Expand Down
Loading