mirror of
https://github.com/Felitendo/spotifast.git
synced 2026-11-01 17:33:26 +01:00
Pressing up or down in a playlist, album or Liked Songs moved focus but left the clicked song selected, so two rows looked selected. The arrows now select only the song they land on, and Shift with up or down extends or shrinks the selection from its anchor. In a playlist you can edit, Delete (and Backspace on macOS) removes the selected songs, like Remove from this playlist. A text field, a dialog or an open menu keeps the key from reaching the list. A refresh that replaces songs without changing the row count clears the selection, so Delete cannot remove songs that took the selected rows' places. The Keyboard shortcuts dialog, README and guide list the new keys, and a test keeps the Wayland Keep Above hint in the translation template.
109 lines
4.0 KiB
Rust
109 lines
4.0 KiB
Rust
/// Catalogs expected to translate every message in the template.
|
|
///
|
|
/// The others are filled in as translators reach them: an empty `msgstr`
|
|
/// compiles out and shows the English source (fastframe-i18n tests that). A
|
|
/// catalog listed here must stay complete, and every catalog must keep the
|
|
/// placeholders of whatever it does translate.
|
|
const COMPLETE: &[&str] = &[
|
|
"de-DE", "es", "fr", "it", "ja", "nl", "pl", "pt-BR", "pt-PT", "ru", "sv", "zh-Hans", "zh-Hant",
|
|
];
|
|
|
|
#[test]
|
|
fn catalogs_cover_the_template_and_preserve_named_placeholders() {
|
|
// A POT leaves these values for msginit. For this comparison its source
|
|
// language is English; the translator's PO carries its own actual rules.
|
|
let template = include_str!("../assets/i18n/spotifast.pot").replace(
|
|
"nplurals=INTEGER; plural=EXPRESSION;",
|
|
"nplurals=2; plural=(n != 1);",
|
|
);
|
|
let template = polib::po_file::parse_from_reader(template.as_bytes()).unwrap();
|
|
assert!(
|
|
template
|
|
.find_message(
|
|
None,
|
|
"On Wayland, use your desktop's Keep Above shortcut or window rule.",
|
|
None,
|
|
)
|
|
.is_some(),
|
|
"the active Wayland hint must remain in the translation template"
|
|
);
|
|
let mut catalogs = 0;
|
|
for file in std::fs::read_dir("assets/i18n").unwrap() {
|
|
let path = file.unwrap().path();
|
|
if path.extension().is_none_or(|ext| ext != "po") {
|
|
continue;
|
|
}
|
|
catalogs += 1;
|
|
let complete = path
|
|
.file_stem()
|
|
.is_some_and(|stem| COMPLETE.contains(&&*stem.to_string_lossy()));
|
|
let translated_catalog = polib::po_file::parse(&path).unwrap();
|
|
assert_eq!(
|
|
template.count(),
|
|
translated_catalog.count(),
|
|
"{}",
|
|
path.display()
|
|
);
|
|
for source in template.messages() {
|
|
let translated = translated_catalog
|
|
.find_message(source.msgctxt(), source.msgid(), source.msgid_plural().ok())
|
|
.unwrap_or_else(|| panic!("missing template entry for {}", source.msgid()));
|
|
assert!(
|
|
!translated.is_fuzzy(),
|
|
"{}: {}",
|
|
path.display(),
|
|
source.msgid()
|
|
);
|
|
if let Ok(forms) = translated.msgstr_plural() {
|
|
assert_eq!(
|
|
forms.len(),
|
|
translated_catalog.metadata.plural_rules.nplurals
|
|
);
|
|
for form in forms {
|
|
assert!(
|
|
!complete || !form.is_empty(),
|
|
"{} claims to be complete but {:?} is untranslated",
|
|
path.display(),
|
|
source.msgid()
|
|
);
|
|
if !form.is_empty() {
|
|
assert_eq!(named_placeholders(form), named_placeholders(source.msgid()));
|
|
}
|
|
}
|
|
} else {
|
|
let text = translated.msgstr().unwrap();
|
|
assert!(
|
|
!complete || !text.is_empty(),
|
|
"{} claims to be complete but {:?} is untranslated",
|
|
path.display(),
|
|
source.msgid()
|
|
);
|
|
if text.is_empty() {
|
|
continue;
|
|
}
|
|
assert_eq!(
|
|
named_placeholders(text),
|
|
named_placeholders(source.msgid()),
|
|
"{}: {}",
|
|
path.display(),
|
|
source.msgid()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
assert_eq!(
|
|
catalogs + 1,
|
|
<spotifast::i18n::Locale as clap::ValueEnum>::value_variants().len()
|
|
);
|
|
}
|
|
|
|
fn named_placeholders(text: &str) -> Vec<&str> {
|
|
let mut names: Vec<_> = text
|
|
.split('{')
|
|
.skip(1)
|
|
.filter_map(|rest| rest.split_once('}').map(|(name, _)| name))
|
|
.collect();
|
|
names.sort_unstable();
|
|
names
|
|
}
|