LRC-LIB-SMARTCOLL: Japanese yes/no rule values read あり / なし

Yes/no rule values used the shared "Yes" / "No" messages, which
Japanese translates as the answers はい / いいえ. As the value of a
condition ("編集あり … いいえ") that reads oddly; あり / なし ("has" /
"has none") is natural.

Messages are keyed by their English text, so the same English word
can't carry two Japanese translations. A small context lookup fixes
that: tr_ctx_in tries "<context>|<source>" first and falls back to the
plain message. Only ja.json adds "smart album value|Yes" / "|No"; the
shared Yes / No (はい / いいえ) stay as they are elsewhere, and every
other language keeps its plain Ja / Sí / Да. A test rejects contextual
keys that name no real value, so a typo can't silently fall back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-09 10:54:56 -04:00
co-authored by Claude Opus 5.5
parent fa982a4cc6
commit c42fc7ac24
2 changed files with 32 additions and 2 deletions
+3 -1
View File
@@ -1741,5 +1741,7 @@
"Portrait (tall)": "縦長",
"Square": "正方形",
"In Color": "カラー",
"No Label": "ラベルなし"
"No Label": "ラベルなし",
"smart album value|Yes": "あり",
"smart album value|No": "なし"
}
+29 -1
View File
@@ -164,6 +164,16 @@ pub fn tr(source: &str) -> &str {
tr_in(language(), source)
}
/// [`tr`] for a message whose translation depends on where it appears: a catalog entry keyed
/// `"<context>|<source>"` wins, else the plain message. A language adds the contextual entry only
/// where its shared translation reads wrongly there (Japanese はい / いいえ as a rule's value).
fn tr_ctx_in<'a>(language: Locale, context: &str, source: &'a str) -> &'a str {
match language.catalog().get(&format!("{context}|{source}")) {
Some(translation) => translation.as_str(),
None => tr_in(language, source),
}
}
/// Catalog values are `'static` (they live in the embedded file), so a translation can be handed
/// out for as long as the `'static` key it was looked up by.
fn tr_in(language: Locale, source: &str) -> &str {
@@ -333,8 +343,11 @@ pub fn bool_text(b: bool) -> &'static str {
bool_text_in(language(), b)
}
/// The context of a smart-album yes/no value ([`tr_ctx_in`]).
const BOOL_CONTEXT: &str = "smart album value";
fn bool_text_in(language: Locale, b: bool) -> &'static str {
tr_in(language, lightcraft_catalog::rules::bool_label(b))
tr_ctx_in(language, BOOL_CONTEXT, lightcraft_catalog::rules::bool_label(b))
}
/// A rule summary for display, keeping free-text rule values verbatim.
@@ -661,6 +674,21 @@ mod tests {
// yes/no fields read Yes / No, never true / false
assert_eq!((bool_text_in(Locale::En, true), bool_text_in(Locale::En, false)), ("Yes", "No"));
assert_eq!(bool_text_in(Locale::De, false), "Nein");
// Japanese answers はい / いいえ don't read as a rule's value: its own wording, あり / なし,
// through a context, while the shared Yes / No keep their meaning everywhere else
assert_eq!((bool_text_in(Locale::Ja, true), bool_text_in(Locale::Ja, false)), ("あり", "なし"));
assert_eq!((Locale::Ja.tr("Yes"), Locale::Ja.tr("No")), ("はい", "いいえ"));
assert_eq!(tr_ctx_in(Locale::Ja, "nope", "Yes"), "はい", "no contextual entry: the plain message");
assert_eq!(tr_ctx_in(Locale::En, BOOL_CONTEXT, "Yes"), "Yes");
// a contextual entry names a real value (a typo would silently fall back to はい / いいえ)
let values = [lightcraft_catalog::rules::bool_label(true), lightcraft_catalog::rules::bool_label(false)];
for language in Locale::ALL {
for key in language.catalog().keys() {
if let Some(value) = key.strip_prefix(BOOL_CONTEXT).and_then(|k| k.strip_prefix('|')) {
assert!(values.contains(&value), "{}: {key:?} isn't a yes/no value", language.code());
}
}
}
for language in Locale::ALL.iter().filter(|language| **language != Locale::En) {
for label in [lightcraft_catalog::rules::bool_label(true), lightcraft_catalog::rules::bool_label(false)] {
assert!(language.catalog().contains_key(label), "{} lacks {label:?}", language.code());