i18n: interface translations are looked up by hash, not by scanning the catalog (#398)

tr() scanned the whole catalog (about 380 rows) for every string a panel
draws, every frame, in Chinese. Each catalog is now indexed once, on
first use, in a HashMap keyed by the English source; English and
languages without rows still skip the lookup. The catalogs already have
no duplicate sources (catalogs_are_unique_and_keep_the_english_shape),
so the index finds the same rows.

Test: the i18n tests (pass-through, tr_args, catalog shape).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-09 23:15:54 +07:00
parent 4fad3e7af4
commit ea5dfae95f
1 changed files with 16 additions and 8 deletions

View File

@ -14,7 +14,9 @@
//! drop a value, and `tr_args` itself cannot panic.
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Display;
use std::sync::OnceLock;
thread_local! {
/// The resolved interface language ("en", "ja", "zh-hans", "zh-hant"); "en" until the first
@ -33,18 +35,24 @@ pub(crate) fn current() -> &'static str {
CURRENT.with(Cell::get)
}
/// The catalog for `language`: empty for English and for any language that has no rows yet.
fn catalog(language: &str) -> &'static [(&'static str, &'static str)] {
match language {
"zh-hans" => SIMPLIFIED_CHINESE,
"zh-hant" => TRADITIONAL_CHINESE,
_ => &[],
}
type Index = HashMap<&'static str, &'static str>;
/// The catalog for `language`, indexed by English source (built on first use: panels translate
/// hundreds of strings a frame); `None` for English and any language that has no rows yet.
fn catalog(language: &str) -> Option<&'static Index> {
static HANS: OnceLock<Index> = OnceLock::new();
static HANT: OnceLock<Index> = OnceLock::new();
let (index, rows) = match language {
"zh-hans" => (&HANS, SIMPLIFIED_CHINESE),
"zh-hant" => (&HANT, TRADITIONAL_CHINESE),
_ => return None,
};
Some(index.get_or_init(|| rows.iter().copied().collect()))
}
/// Translate one interface string: the current language's row for `text`, or `text` itself.
pub(crate) fn tr(text: &str) -> &str {
catalog(current()).iter().find(|(en, _)| *en == text).map_or(text, |(_, translated)| *translated)
catalog(current()).and_then(|c| c.get(text)).copied().unwrap_or(text)
}
/// Translate an interface string that carries values: each `{}` takes the next argument. A missing