Switch repo to English, add i18n with EN/DE language files and first-start language selection
- in-game texts moved to lang/en.lang and lang/de.lang (key = text, placeholders) - game logic emits message keys + args, translated at display time - localStorage access via own miniquad JS plugin (www/dc_plugin.js) - code comments, DESIGN.md, CREDITS.md translated; README.md added Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a101f6ddc4
commit
fbf6f223fc
18 files changed
+1072
-362
No files matched your search
+87
@@ -0,0 +1,87 @@
|
||||
// Internationalisation: in-game texts live in lang/*.lang files (key = text).
|
||||
// English is the reference language and the fallback for missing keys.
|
||||
// To add a language, create lang/<code>.lang and add it to SOURCES.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const SOURCES: &[(&str, &str)] = &[("en", include_str!("../lang/en.lang")), ("de", include_str!("../lang/de.lang"))];
|
||||
|
||||
struct I18n {
|
||||
langs: Vec<(String, HashMap<String, String>)>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
fn parse(src: &str) -> HashMap<String, String> {
|
||||
let mut m = HashMap::new();
|
||||
for line in src.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
if let Some((k, v)) = line.split_once('=') {
|
||||
m.insert(k.trim().to_string(), v.trim().to_string());
|
||||
}
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static I18N: RefCell<I18n> = RefCell::new(I18n {
|
||||
langs: SOURCES.iter().map(|(code, src)| (code.to_string(), parse(src))).collect(),
|
||||
current: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Available languages: (code, display name in that language)
|
||||
pub fn languages() -> Vec<(String, String)> {
|
||||
I18N.with(|i| {
|
||||
i.borrow().langs.iter().map(|(c, m)| (c.clone(), m.get("lang.name").cloned().unwrap_or_else(|| c.clone()))).collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_language(code: &str) -> bool {
|
||||
I18N.with(|i| {
|
||||
let mut i = i.borrow_mut();
|
||||
if let Some(k) = i.langs.iter().position(|(c, _)| c == code) {
|
||||
i.current = k;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // used by the settings menu
|
||||
pub fn language() -> String {
|
||||
I18N.with(|i| {
|
||||
let i = i.borrow();
|
||||
i.langs[i.current].0.clone()
|
||||
})
|
||||
}
|
||||
|
||||
// Translate a key (fallback: English, then the key itself)
|
||||
pub fn tr(key: &str) -> String {
|
||||
I18N.with(|i| {
|
||||
let i = i.borrow();
|
||||
i.langs[i.current].1.get(key).or_else(|| i.langs[0].1.get(key)).cloned().unwrap_or_else(|| key.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
// Translate with arguments. Arguments starting with '@' are keys and get translated too.
|
||||
pub fn trf(key: &str, args: &[String]) -> String {
|
||||
let mut s = tr(key);
|
||||
for (n, a) in args.iter().enumerate() {
|
||||
let v = if let Some(k) = a.strip_prefix('@') { tr(k) } else { a.clone() };
|
||||
s = s.replace(&format!("{{{}}}", n), &v);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
// Translate a key in a specific language (used by the language selection dialog)
|
||||
pub fn tr_in(code: &str, key: &str) -> String {
|
||||
I18N.with(|i| {
|
||||
let i = i.borrow();
|
||||
i.langs.iter().find(|(c, _)| c == code).and_then(|(_, m)| m.get(key)).cloned().unwrap_or_else(|| key.to_string())
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user