fix(i18n): fill placeholders in one pass (#782) (#1063)

A value holding {other} was expanded again by a later argument. fmt now
walks the template once, so values stay exactly as they came.
This commit is contained in:
VoiceLessQ
2026-11-01 07:54:15 -02:00
committed by GitHub
parent 82b60070f6
commit 7eeb66d01f
2 changed files with 27 additions and 5 deletions
+13 -1
View File
@@ -5,7 +5,19 @@ use std::collections::{BTreeSet, HashSet};
use super::catalog::{Catalog, unescape};
use super::lang::{name_sort_key, plural_czech, plural_east_slavic, plural_polish};
use super::{LANGUAGES, Lang, LangInfo, lang_from_tag, message, tr, trn};
use super::{LANGUAGES, Lang, LangInfo, fmt, lang_from_tag, message, tr, trn};
#[test]
fn a_filled_in_value_is_never_read_as_a_placeholder() {
assert_eq!(fmt("{name}: {error}", &[("name", "{error}"), ("error", "disk full")]), "{error}: disk full");
assert_eq!(fmt("{a} and {a}", &[("a", "{a}")]), "{a} and {a}");
}
#[test]
fn placeholders_nobody_filled_stay_as_written() {
assert_eq!(fmt("{a} {b} { {} }{", &[("a", "1")]), "1 {b} { {} }{");
assert_eq!(fmt("{{a}", &[("a", "1")]), "{1");
}
/// Each catalog's rows as (line number, context, source, translation), split exactly as the
/// parser does: `trim_end` only, since a plain row *starts* with a TAB.
+14 -4
View File
@@ -53,12 +53,22 @@ pub fn t_id<'a>(id: &str, label: &'a str) -> &'a str {
#[cfg(test)]
pub(crate) use strict::{StrictCjkDrawing, drew_cjk};
/// Fill `{name}` placeholders. Unknown placeholders are left as written.
/// Fill `{name}` placeholders in one pass. Values and unknown placeholders stay as written.
pub fn fmt(template: &str, args: &[(&str, &str)]) -> String {
let mut out = template.to_string();
for (k, v) in args {
out = out.replace(&format!("{{{k}}}"), v);
let mut out = String::with_capacity(template.len());
let mut rest = template;
while let Some((before, after)) = rest.split_once('{') {
out.push_str(before);
let filled = after.split_once('}').and_then(|(name, tail)| args.iter().find(|(k, _)| *k == name).map(|(_, v)| (*v, tail)));
if let Some((value, tail)) = filled {
out.push_str(value);
rest = tail;
} else {
out.push('{');
rest = after;
}
}
out.push_str(rest);
out
}