Hide Craft apps the user is not interested in. (#185)
* Add a Polish interface
- pl.tsv: the full catalog (287 rows), written clean-room from the English
labels; plurals use the same one|few|many rule as Russian.
- Registered as the seventh language: "Polski" in the picker, locales pl,
pl-PL and pl_PL.UTF-8, complete_menus = true so the completeness tests
cover it.
- tests/polish.rs: the Polish UI test, modeled on the French one.
- README and AGENTS updated for the new language.
* Add Hungarian localization and fix responsive settings layout
* i18n: add Czech (cs) interface language
Czech catalog with every key of the most complete catalog (287 rows), the
cs language entry with locale detection (cs-CZ, cs_CZ.UTF-8, ?lang=cs),
a Czech plural rule (1 / 2-4 / other) with a unit test, and the README
language list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: add Japanese (ja) interface language
ja.tsv translates the whole interface, the tray menu and tooltip, each app's
tagline and the backend's install/update messages into Japanese, and
Settings > Language offers 日本語. Japanese locales (ja, ja-JP, ja_JP.UTF-8…)
select it; Japanese has one plural form.
Japanese is a CJK language, so cjk_font.rs now looks for a Japanese system
font (Hiragino Sans on macOS; Yu Gothic, Meiryo or MS Gothic on Windows;
Noto Sans CJK JP, Noto Sans JP, IPAGothic, Takao or VL Gothic on Linux and
FreeBSD), falling back to a Chinese one, which draws kana and most kanji,
where the system has none. The font is loaded per language, so switching
between Japanese and Chinese reloads the right face.
A fallback face is nudged onto the body font's baseline: egui puts a fallback
glyph's baseline at that face's own ascent and only centres the two line
boxes, which left a face whose metrics differ from Inter's riding high. On
Windows, Yu Gothic's line gap is half an em, so its kana and kanji sat about
0.29 em above the Latin text beside them (Microsoft YaHei, used for Chinese,
happens to match). The nudge, applied through a FontTweak, is the difference
in ascent and line height, and helps the Chinese faces too.
The README and AGENTS list the new language. Tests: the complete-catalog
checks cover ja.tsv (placeholders, plural forms, every tl! literal, taglines,
package formats), plus a locale/plural test, a Japanese glyph-coverage test,
a baseline test and tests/japanese.rs (grid, table, dates, a backend message).
* i18n: make the CJK font choice language-neutral
The CJK comments, the missing-font note and the plural-rule docs named the
languages, so each new one meant editing them all; they now say "CJK". Each
language's writing system (its `script`) now lives in the registry, and a
language is drawn only with a face for its own script — never another's,
which would leave characters as tofu — so a language the system has no font
for stays in English, like any other. Adding Korean or Traditional Chinese is
a registry row and a candidate list, not a special case.
* Hide Craft apps the user is not interested in.
* i18n(pl): use Polish plural rule, so 21 is "21 aplikacji"
plural_east_slavic treats 21, 31 and 101 as "one", which is right for
Russian but not Polish, where only 1 itself takes the singular.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(cs): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(pl): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(hu): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(ja): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: hide/unhide strings for the Spanish, Czech, Polish, Hungarian and Japanese catalogs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* hungarian test: count only the settings body's links, not the cards' Website links behind the modal
#198 gave every card a Website link, and Hungarian labels both Webhely.
* hungarian test: card links are 14px text, settings icons 24px; filter on that
---------
Co-authored-by: DzikStar <82441193+DzikStar@users.noreply.github.com>
Co-authored-by: ferivoq <ferikegameplay@gmail.com>
Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: leonaci <leonacism@gmail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
93f12a5d9e
commit
250cc54335
|
|
@ -182,9 +182,10 @@ impl LauncherUi {
|
|||
self.toasts.clear();
|
||||
}
|
||||
|
||||
/// Installed apps with an update waiting (the tray shows the count).
|
||||
/// Installed apps with an update waiting (the tray shows the count). Hidden apps are left out,
|
||||
/// so the badge matches the Updates list.
|
||||
pub fn update_count(&self) -> usize {
|
||||
self.entries.iter().filter(|e| e.status(self.platform, self.caps.manage_apps) == AppStatus::UpdateAvailable).count()
|
||||
self.entries.iter().filter(|e| self.in_filter(e, Filter::Updates)).count()
|
||||
}
|
||||
|
||||
/// Something is downloading or installing, the launcher's own update included: quitting now
|
||||
|
|
@ -377,7 +378,7 @@ impl LauncherUi {
|
|||
fn announce_updates(&mut self, host: &mut dyn Host) {
|
||||
let mut fresh = Vec::new();
|
||||
for e in &self.entries {
|
||||
if e.status(self.platform, self.caps.manage_apps) != AppStatus::UpdateAvailable {
|
||||
if !self.in_filter(e, Filter::Updates) {
|
||||
continue;
|
||||
}
|
||||
let Some(v) = e.latest_label() else { continue };
|
||||
|
|
@ -414,7 +415,7 @@ impl LauncherUi {
|
|||
let to_update: Vec<(String, String)> = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|e| e.status(self.platform, true) == AppStatus::UpdateAvailable && e.job.is_none() && !e.ready)
|
||||
.filter(|e| self.in_filter(e, Filter::Updates) && e.job.is_none() && !e.ready)
|
||||
.filter_map(|e| {
|
||||
let v = e.latest_label()?;
|
||||
if self.auto_update_attempted.contains(&(e.app.id.to_string(), v.clone())) { None } else { Some((e.app.id.to_string(), v)) }
|
||||
|
|
@ -517,14 +518,8 @@ impl LauncherUi {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| {
|
||||
let s = e.status(self.platform, self.caps.manage_apps);
|
||||
let keep = match self.settings.filter {
|
||||
Filter::All => true,
|
||||
Filter::Installed => s.is_installed(),
|
||||
Filter::Updates => s == AppStatus::UpdateAvailable,
|
||||
Filter::NotInstalled => !s.is_installed(),
|
||||
};
|
||||
keep && (needle.is_empty() || e.app.name.to_lowercase().contains(&needle) || e.app.tagline.to_lowercase().contains(&needle))
|
||||
self.in_filter(e, self.settings.filter)
|
||||
&& (needle.is_empty() || e.app.name.to_lowercase().contains(&needle) || e.app.tagline.to_lowercase().contains(&needle))
|
||||
})
|
||||
.collect();
|
||||
let platform = self.platform;
|
||||
|
|
@ -591,18 +586,20 @@ impl LauncherUi {
|
|||
}
|
||||
|
||||
pub(crate) fn count(&self, filter: Filter) -> usize {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let s = e.status(self.platform, self.caps.manage_apps);
|
||||
match filter {
|
||||
Filter::All => true,
|
||||
Filter::Installed => s.is_installed(),
|
||||
Filter::Updates => s == AppStatus::UpdateAvailable,
|
||||
Filter::NotInstalled => !s.is_installed(),
|
||||
}
|
||||
})
|
||||
.count()
|
||||
self.entries.iter().filter(|e| self.in_filter(e, filter)).count()
|
||||
}
|
||||
|
||||
/// Whether `entry` belongs in `filter`. Hidden apps stay out of every list except Hidden.
|
||||
pub(crate) fn in_filter(&self, entry: &AppEntry, filter: Filter) -> bool {
|
||||
let hidden = self.settings.hidden.contains(entry.app.id);
|
||||
let status = entry.status(self.platform, self.caps.manage_apps);
|
||||
match filter {
|
||||
Filter::Hidden => hidden,
|
||||
Filter::All => !hidden,
|
||||
Filter::Installed => !hidden && status.is_installed(),
|
||||
Filter::Updates => !hidden && status == AppStatus::UpdateAvailable,
|
||||
Filter::NotInstalled => !hidden && !status.is_installed(),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_toasts(&mut self, ctx: &egui::Context) {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@
|
|||
Apps on this computer Aplikace v tomto počítači
|
||||
Installed apps with a newer version Nainstalované aplikace s novější verzí
|
||||
Apps you can install Aplikace, které lze nainstalovat
|
||||
Hide App Skrýt aplikaci
|
||||
Unhide App Zobrazit aplikaci
|
||||
Hidden Apps Skryté aplikace
|
||||
Apps you hid Aplikace, které jste skryli
|
||||
Checked {ago} · using {sources} Zkontrolováno {ago} · zdroje: {sources}
|
||||
{a} and {b} {a} a {b}
|
||||
Join the Discord Připojit se na Discord
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 8.
|
|
|
@ -118,6 +118,10 @@
|
|||
Apps on this computer Apps auf diesem Computer
|
||||
Installed apps with a newer version Installierte Apps mit neuerer Version
|
||||
Apps you can install Apps, die du installieren kannst
|
||||
Hide App App ausblenden
|
||||
Unhide App App einblenden
|
||||
Hidden Apps Ausgeblendete Apps
|
||||
Apps you hid Apps, die du ausgeblendet hast
|
||||
Checked {ago} · using {sources} Zuletzt geprüft: {ago} · über {sources}
|
||||
{a} and {b} {a} und {b}
|
||||
Join the Discord Dem Discord beitreten
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 11 and column 16.
|
|
|
@ -117,6 +117,10 @@
|
|||
Apps on this computer Aplicaciones en este equipo
|
||||
Installed apps with a newer version Aplicaciones instaladas con una versión más reciente
|
||||
Apps you can install Aplicaciones que puedes instalar
|
||||
Hide App Ocultar aplicación
|
||||
Unhide App Mostrar aplicación
|
||||
Hidden Apps Aplicaciones ocultas
|
||||
Apps you hid Aplicaciones que ocultaste
|
||||
Checked {ago} · using {sources} Comprobado {ago} · con {sources}
|
||||
{a} and {b} {a} y {b}
|
||||
Join the Discord Únete al Discord
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 5 and column 48.
|
|
|
@ -116,6 +116,10 @@
|
|||
Apps on this computer Applications sur cet ordinateur
|
||||
Installed apps with a newer version Applications installées avec une version plus récente
|
||||
Apps you can install Applications que vous pouvez installer
|
||||
Hide App Masquer l'application
|
||||
Unhide App Afficher l'application
|
||||
Hidden Apps Applications masquées
|
||||
Apps you hid Applications que vous avez masquées
|
||||
Checked {ago} · using {sources} Vérifié {ago} · via {sources}
|
||||
{a} and {b} {a} et {b}
|
||||
Join the Discord Rejoindre le Discord
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 5 and column 50.
|
|
|
@ -118,6 +118,10 @@
|
|||
Apps on this computer Alkalmazások ezen a számítógépen
|
||||
Installed apps with a newer version Telepített alkalmazások, amelyekhez frissítés érhető el
|
||||
Apps you can install Telepíthető alkalmazások
|
||||
Hide App Alkalmazás elrejtése
|
||||
Unhide App Alkalmazás megjelenítése
|
||||
Hidden Apps Rejtett alkalmazások
|
||||
Apps you hid Az általad elrejtett alkalmazások
|
||||
Checked {ago} · using {sources} Ellenőrizve: {ago} · {sources}
|
||||
{a} and {b} {a} és {b}
|
||||
Join the Discord Csatlakozás a Discordhoz
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 11 and column 16.
|
|
|
@ -120,6 +120,10 @@
|
|||
Apps on this computer このコンピューターのアプリ
|
||||
Installed apps with a newer version 新しいバージョンがあるインストール済みアプリ
|
||||
Apps you can install インストールできるアプリ
|
||||
Hide App アプリを非表示
|
||||
Unhide App アプリを再表示
|
||||
Hidden Apps 非表示のアプリ
|
||||
Apps you hid 非表示にしたアプリ
|
||||
Checked {ago} · using {sources} 最終確認: {ago} · 取得元: {sources}
|
||||
{a} and {b} {a} と {b}
|
||||
Join the Discord Discord に参加
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 14 and column 44.
|
|
|
@ -119,6 +119,10 @@
|
|||
Apps on this computer Aplikacje na tym komputerze
|
||||
Installed apps with a newer version Zainstalowane aplikacje z nowszą wersją
|
||||
Apps you can install Aplikacje, które możesz zainstalować
|
||||
Hide App Ukryj aplikację
|
||||
Unhide App Pokaż aplikację
|
||||
Hidden Apps Ukryte aplikacje
|
||||
Apps you hid Aplikacje ukryte przez ciebie
|
||||
Checked {ago} · using {sources} Ostatnio sprawdzono {ago} · przy użyciu {sources}
|
||||
{a} and {b} {a} i {b}
|
||||
Join the Discord Dołącz na Discorda
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 11 and column 15.
|
|
|
@ -106,6 +106,10 @@
|
|||
Apps on this computer Aplicativos neste computador
|
||||
Installed apps with a newer version Aplicativos instalados com nova versão
|
||||
Apps you can install Aplicativos que você pode instalar
|
||||
Hide App Ocultar aplicativo
|
||||
Unhide App Mostrar aplicativo
|
||||
Hidden Apps Aplicativos ocultos
|
||||
Apps you hid Aplicativos que você ocultou
|
||||
Checked {ago} · using {sources} Verificado {ago} · via {sources}
|
||||
{a} and {b} {a} e {b}
|
||||
Join the Discord Entrar no Discord
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 9.
|
|
|
@ -116,6 +116,10 @@
|
|||
Apps on this computer Приложения на этом компьютере
|
||||
Installed apps with a newer version Установленные приложения, у которых есть новая версия
|
||||
Apps you can install Приложения, которые можно установить
|
||||
Hide App Скрыть приложение
|
||||
Unhide App Показать приложение
|
||||
Hidden Apps Скрытые приложения
|
||||
Apps you hid Приложения, которые вы скрыли
|
||||
Checked {ago} · using {sources} Проверено {ago} · через {sources}
|
||||
{a} and {b} {a} и {b}
|
||||
Join the Discord Присоединиться к Discord
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 13 and column 44.
|
|
|
@ -262,6 +262,10 @@ soundcraft Recording, editing and mixing audio 音频录制、编辑与混音
|
|||
Apps on this computer 此电脑上的应用
|
||||
Installed apps with a newer version 有新版本的已安装应用
|
||||
Apps you can install 可安装的应用
|
||||
Hide App 隐藏应用
|
||||
Unhide App 取消隐藏应用
|
||||
Hidden Apps 已隐藏的应用
|
||||
Apps you hid 你隐藏的应用
|
||||
Cancel update 取消更新
|
||||
Stop waiting; the update can be started again later 停止等待;稍后可重新开始更新
|
||||
Download again 重新下载
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 4 and column 3.
|
|
|
@ -22,6 +22,8 @@ pub enum Filter {
|
|||
Installed,
|
||||
Updates,
|
||||
NotInstalled,
|
||||
/// Apps the user chose to hide from the other lists.
|
||||
Hidden,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -110,6 +112,8 @@ pub struct Settings {
|
|||
pub notified: BTreeMap<String, String>,
|
||||
/// Unix seconds of the last finished check.
|
||||
pub last_check: Option<i64>,
|
||||
/// Catalog ids of apps hidden from the main lists (`photocraft`, …).
|
||||
pub hidden: std::collections::BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
|
@ -181,6 +185,7 @@ impl Default for Settings {
|
|||
dismissed_notices: std::collections::BTreeSet::new(),
|
||||
notified: BTreeMap::new(),
|
||||
last_check: None,
|
||||
hidden: std::collections::BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ impl LauncherUi {
|
|||
}
|
||||
|
||||
pub(crate) fn toolbar(&mut self, ui: &mut Ui) {
|
||||
if self.settings.filter == Filter::Hidden && self.settings.hidden.is_empty() {
|
||||
self.settings.filter = Filter::All;
|
||||
}
|
||||
let t = Tokens::get(ui.ctx());
|
||||
if self.dialog.is_none()
|
||||
&& let Some(r) = self.launcher_update.clone()
|
||||
|
|
@ -195,17 +198,19 @@ impl LauncherUi {
|
|||
self.announcements(ui);
|
||||
ui.horizontal(|ui| {
|
||||
let updates = self.count(Filter::Updates);
|
||||
let filters: Vec<(Filter, Cell<'_>)> = [
|
||||
let mut filters = vec![
|
||||
(Filter::All, tl!("All"), tl!("Every Craft app"), None),
|
||||
// The tab names a group ("installed ones"); the status pill says "Installed".
|
||||
(Filter::Installed, tr_ctx(current(), "filter", "Installed"), tl!("Apps on this computer"), None),
|
||||
(Filter::Updates, tl!("Updates"), tl!("Installed apps with a newer version"), Some(t.warn)),
|
||||
(Filter::NotInstalled, tl!("Not installed"), tl!("Apps you can install"), None),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|(f, ..)| self.caps.manage_apps || *f == Filter::All)
|
||||
.map(|(f, label, tip, highlight)| (f, Cell { label, tip, count: Some((self.count(f), highlight)) }))
|
||||
.collect();
|
||||
];
|
||||
filters.retain(|(f, ..)| self.caps.manage_apps || *f == Filter::All);
|
||||
if !self.settings.hidden.is_empty() {
|
||||
filters.push((Filter::Hidden, tl!("Hidden Apps"), tl!("Apps you hid"), None));
|
||||
}
|
||||
let filters: Vec<(Filter, Cell<'_>)> =
|
||||
filters.into_iter().map(|(f, label, tip, highlight)| (f, Cell { label, tip, count: Some((self.count(f), highlight)) })).collect();
|
||||
widgets::cells(ui, &mut self.settings.filter, &filters);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if self.caps.manage_apps && updates > 0 {
|
||||
|
|
@ -215,12 +220,8 @@ impl LauncherUi {
|
|||
fmt(tl!("Update all ({count})"), &[("count", &updates.to_string())])
|
||||
};
|
||||
if widgets::button(ui, &label, Kind::Primary, 0.0).clicked() {
|
||||
let ids: Vec<&str> = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|e| e.status(self.platform, true) == AppStatus::UpdateAvailable && e.job.is_none())
|
||||
.map(|e| e.app.id)
|
||||
.collect();
|
||||
let ids: Vec<&str> =
|
||||
self.entries.iter().filter(|e| self.in_filter(e, Filter::Updates) && e.job.is_none()).map(|e| e.app.id).collect();
|
||||
for id in ids {
|
||||
self.send(Command::Install { app: id.to_string() });
|
||||
}
|
||||
|
|
@ -838,6 +839,19 @@ impl LauncherUi {
|
|||
link(ui, tl!("Website"), entry.app.website());
|
||||
link(ui, tl!("GitHub repository"), entry.app.repo_url());
|
||||
link(ui, tl!("All releases"), entry.app.releases_url());
|
||||
ui.separator();
|
||||
let hidden = self.settings.hidden.contains(entry.app.id);
|
||||
if ui.button(if hidden { tl!("Unhide App") } else { tl!("Hide App") }).clicked() {
|
||||
if hidden {
|
||||
self.settings.hidden.remove(entry.app.id);
|
||||
if self.settings.hidden.is_empty() && self.settings.filter == Filter::Hidden {
|
||||
self.settings.filter = Filter::All;
|
||||
}
|
||||
} else {
|
||||
self.settings.hidden.insert(id);
|
||||
}
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,6 +390,35 @@ fn table_view_and_light_theme() {
|
|||
assert!(sent.borrow().iter().any(|c| matches!(c, Command::Launch { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hiding_an_app_removes_it_until_it_is_unhidden() {
|
||||
let (mut h, _) = harness(ViewMode::Table, egui::Theme::Dark, true);
|
||||
h.run_steps(6);
|
||||
// PhotoCraft has an update, so hiding it also drops the Updates count.
|
||||
h.get_by_label_contains("Updates 2");
|
||||
let row = h.get_all_by_label("PhotoCraft").next().unwrap().rect().center().y;
|
||||
h.get_all_by_label("…").find(|n| (n.rect().center().y - row).abs() < 25.0).unwrap().click();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Hide App").click();
|
||||
h.run_steps(4);
|
||||
assert!(h.query_by_label("PhotoCraft").is_none());
|
||||
assert!(h.state().as_ref().unwrap().settings.hidden.contains("photocraft"));
|
||||
h.get_by_label_contains("Updates 1");
|
||||
h.get_by_label_contains("Hidden Apps").click();
|
||||
h.run_steps(4);
|
||||
h.get_by_label("PhotoCraft");
|
||||
assert!(h.query_by_label("WordCraft").is_none());
|
||||
let row = h.get_all_by_label("PhotoCraft").next().unwrap().rect().center().y;
|
||||
h.get_all_by_label("…").find(|n| (n.rect().center().y - row).abs() < 25.0).unwrap().click();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Unhide App").click();
|
||||
h.run_steps(4);
|
||||
h.get_by_label("PhotoCraft");
|
||||
assert!(h.query_by_label_contains("Hidden Apps").is_none());
|
||||
assert!(h.state().as_ref().unwrap().settings.hidden.is_empty());
|
||||
assert_eq!(h.state().as_ref().unwrap().settings.filter, artcraft_launcher_ui_egui::settings::Filter::All);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switching_views_and_filters() {
|
||||
let (mut h, _) = harness(ViewMode::Grid, egui::Theme::Dark, true);
|
||||
|
|
|
|||
Loading…
Reference in New Issue