diff options
| author | metamuffin <metamuffin@disroot.org> | 2025-10-24 20:53:08 +0200 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2025-10-24 20:53:08 +0200 |
| commit | e67d2d03f9e2d66a24a6b7561146af589e019891 (patch) | |
| tree | d776ae10122b569f6d8faf36cd01088ce1231afc | |
| parent | 1e5dc0dee2fed17d6cc5c0e98edbb9b72daa6345 (diff) | |
| download | hurrycurry-e67d2d03f9e2d66a24a6b7561146af589e019891.tar hurrycurry-e67d2d03f9e2d66a24a6b7561146af589e019891.tar.bz2 hurrycurry-e67d2d03f9e2d66a24a6b7561146af589e019891.tar.zst | |
Localize book html export
| -rw-r--r-- | makefile | 6 | ||||
| -rw-r--r-- | server/book-export/src/main.rs | 11 | ||||
| -rw-r--r-- | server/locale/src/error.rs | 66 | ||||
| -rw-r--r-- | server/locale/src/lib.rs | 148 | ||||
| -rw-r--r-- | server/locale/src/macros.rs | 51 | ||||
| -rw-r--r-- | server/src/main.rs | 78 | ||||
| -rw-r--r-- | server/src/server.rs | 4 | ||||
| -rw-r--r-- | server/tools/src/map_linter.rs | 4 |
8 files changed, 207 insertions, 161 deletions
@@ -28,7 +28,7 @@ DISCOVER = target/release/hurrycurry-discover EDITOR = target/release/hurrycurry-editor ALL_TESTCLIENT = test-client/main.js $(patsubst locale/%.ini,test-client/locale/%.json,$(wildcard locale/*.ini)) -ALL_BOOK = target/book/book.html +ALL_BOOK = target/book/book.html $(patsubst locale/%.ini,target/book/book.%.html,$(wildcard locale/*.ini)) ALL_SERVER = $(SERVER) $(TOOLS) $(REPLAYTOOL) $(EDITOR) $(DISCOVER) $(LOCALE_EXPORT) ALL_CLIENT = client/.godot/import-finished client/icons/adaptive-background.png client/icons/adaptive-foreground.png ALL_DATA = data/recipes/none.yaml data/recipes/anticurry.yaml data/recipes/default.yaml @@ -54,8 +54,8 @@ target/book/tiles/done: target/book/tiles/list client/.godot/import-finished $(CLIENT) --render-tiles ../$< --render-output ../$(dir $@) --render-resolution 512; touch $@ target/book/items/done: target/book/items/list client/.godot/import-finished $(CLIENT) --render-items ../$< --render-output ../$(dir $@) --render-resolution 512; touch $@ -target/book/book.html: target/book/tiles/done target/book/items/done $(BOOK_EXPORT) $(ALL_DATA) - $(BOOK_EXPORT) -f html -m 5star > $@~ && cp $@~ $@ +target/book/book.%.html: target/book/tiles/done target/book/items/done $(BOOK_EXPORT) $(ALL_DATA) locale/en.ini locale/%.ini + $(BOOK_EXPORT) -l $(shell basename -s .html $@ | cut -d '.' -f 2) -f html -m 5star > $@~ && cp $@~ $@ data/recipes/none.yaml: echo > $@ diff --git a/server/book-export/src/main.rs b/server/book-export/src/main.rs index 9d3cb5dc..b2731308 100644 --- a/server/book-export/src/main.rs +++ b/server/book-export/src/main.rs @@ -22,10 +22,15 @@ use crate::book_html::render_html_book; use anyhow::Result; use clap::{Parser, ValueEnum}; use hurrycurry_data::{book::book, index::DataIndex}; -use hurrycurry_locale::FALLBACK_LOCALE; +use hurrycurry_locale::Locale; +use std::path::PathBuf; #[derive(Parser)] struct Args { + #[arg(long, default_value = "locale")] + locale_dir: PathBuf, + #[arg(short, long, default_value = "en")] + locale: String, #[arg(short, long)] format: ExportFormat, #[arg(short, long, default_value = "5star")] @@ -52,7 +57,9 @@ fn main() -> Result<()> { println!("{}", serde_json::to_string(&book).unwrap()) } ExportFormat::Html => { - println!("{}", render_html_book(&data, &book, &FALLBACK_LOCALE)); + let mut locale = Locale::load(&args.locale_dir.join(format!("{}.ini", args.locale)))?; + locale.merge(Locale::load(&args.locale_dir.join("en.ini"))?); + println!("{}", render_html_book(&data, &book, &locale)); } } Ok(()) diff --git a/server/locale/src/error.rs b/server/locale/src/error.rs new file mode 100644 index 00000000..7e41519d --- /dev/null +++ b/server/locale/src/error.rs @@ -0,0 +1,66 @@ +/* + Hurry Curry! - a game about cooking + Copyright (C) 2025 Hurry Curry! Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ + +use crate::tr; +use hurrycurry_protocol::Message; +use std::fmt::{Debug, Display}; + +pub enum TrError { + Tr { + id: &'static str, + params: Vec<Message>, + }, + Plain(String), +} +impl From<TrError> for Message { + fn from(value: TrError) -> Self { + match value { + TrError::Tr { id, params } => Self::Translation { + id: id.to_owned(), + params, + }, + TrError::Plain(s) => Self::Text(s), + } + } +} +impl Debug for TrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrError::Tr { id, params } => write!(f, "{} {:?}", tr(id), params), + TrError::Plain(s) => write!(f, "{s}"), + } + } +} +impl Display for TrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrError::Tr { id, params } => { + if params.is_empty() { + write!(f, "{}", tr(id)) + } else { + let mut s = tr(id).to_string(); + for (i, p) in params.iter().enumerate() { + s = s.replace(&format!("{{{i}}}"), &format!("{p:?}")); + } + write!(f, "{s}") + } + } + TrError::Plain(s) => write!(f, "{s}"), + } + } +} diff --git a/server/locale/src/lib.rs b/server/locale/src/lib.rs index 9d55c7cd..0bf4bf80 100644 --- a/server/locale/src/lib.rs +++ b/server/locale/src/lib.rs @@ -16,113 +16,14 @@ */ +pub mod error; +pub use error::*; pub mod message; +#[macro_use] +pub mod macros; -use anyhow::anyhow; -use hurrycurry_protocol::Message; -use std::{ - collections::HashMap, - fmt::{Debug, Display}, - ops::Index, - sync::LazyLock, -}; - -#[macro_export] -macro_rules! trm { - ($id:literal $(, $typ:ident = $param:expr)*) => { - hurrycurry_protocol::Message::Translation { - id: $id.to_owned(), - params: vec![$($crate::trm_param!($typ, $param)),*] - } - }; -} - -#[macro_export] -macro_rules! trm_param { - (s, $x:expr) => { - hurrycurry_protocol::Message::Text($x) - }; - (i, $x:expr) => { - hurrycurry_protocol::Message::Item($x) - }; - (t, $x:expr) => { - hurrycurry_protocol::Message::Tile($x) - }; - (m, $x:expr) => { - $x - }; -} - -pub enum TrError { - Tr { - id: &'static str, - params: Vec<Message>, - }, - Plain(String), -} -impl From<TrError> for Message { - fn from(value: TrError) -> Self { - match value { - TrError::Tr { id, params } => Self::Translation { - id: id.to_owned(), - params, - }, - TrError::Plain(s) => Self::Text(s), - } - } -} -impl Debug for TrError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TrError::Tr { id, params } => write!(f, "{} {:?}", tr(id), params), - TrError::Plain(s) => write!(f, "{s}"), - } - } -} -impl Display for TrError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TrError::Tr { id, params } => { - if params.is_empty() { - write!(f, "{}", tr(id)) - } else { - let mut s = tr(id).to_string(); - for (i, p) in params.iter().enumerate() { - s = s.replace(&format!("{{{i}}}"), &format!("{p:?}")); - } - write!(f, "{s}") - } - } - TrError::Plain(s) => write!(f, "{s}"), - } - } -} - -#[macro_export] -macro_rules! tre { - ($id:literal $(, $typ:ident = $param:expr)*) => { - $crate::TrError::Tr { - id: $id, - params: vec![$($crate::tre_param!($typ, $param)),*] - } - }; -} - -#[macro_export] -macro_rules! tre_param { - (s, $x:expr) => { - hurrycurry_protocol::Message::Text($x) - }; - (i, $x:expr) => { - hurrycurry_protocol::Message::Item($x) - }; - (t, $x:expr) => { - hurrycurry_protocol::Message::Tile($x) - }; - (m, $x:expr) => { - $x - }; -} +use anyhow::{Error, Result, anyhow}; +use std::{collections::HashMap, fs::read_to_string, ops::Index, path::Path, sync::LazyLock}; pub struct Locale(HashMap<String, String>); impl Index<&'static str> for Locale { @@ -133,17 +34,36 @@ impl Index<&'static str> for Locale { } impl Locale { - pub fn load() -> anyhow::Result<Self> { + // pub fn load_from_env() -> Result<Self> { + // if let Some(dir) = option_env!("LOCALE_DIR") { + // let mut test_order = Vec::new(); + // test_order.extend(var("LC_MESSAGE").ok()); + // test_order.extend(var("LC_ALL").or(var("LANG")).ok()); + // test_order.push("en_US.UTF-8".to_string()); + // } else { + // Ok(Self::load_fallback()?) + // } + // } + pub fn merge(&mut self, other: Self) { + for (k, v) in other.0 { + if !self.0.contains_key(&k) { + self.0.insert(k, v); + } + } + } + pub fn load(path: &Path) -> Result<Self> { + Self::parse(&read_to_string(path)?) + } + pub fn load_fallback() -> Result<Self> { + Self::parse(include_str!("../../../locale/en.ini")) + } + pub fn parse(ini: &str) -> Result<Self> { Ok(Self( - include_str!("../../../locale/en.ini") - .lines() + ini.lines() .skip(1) .map(|l| { let (k, v) = l.split_once("=").ok_or(anyhow!("'=' missing"))?; - Ok::<_, anyhow::Error>(( - k.trim_end().to_owned(), - v.trim_start().replace("%n", "\n"), - )) + Ok::<_, Error>((k.trim_end().to_owned(), v.trim_start().replace("%n", "\n"))) }) .collect::<anyhow::Result<HashMap<_, _>>>()?, )) @@ -153,7 +73,7 @@ impl Locale { } } -pub static FALLBACK_LOCALE: LazyLock<Locale> = LazyLock::new(|| Locale::load().unwrap()); +pub static GLOBAL_LOCALE: LazyLock<Locale> = LazyLock::new(|| Locale::load_fallback().unwrap()); pub fn tr(s: &'static str) -> &'static str { - &FALLBACK_LOCALE[s] + &GLOBAL_LOCALE[s] // TODO crash or placeholder? } diff --git a/server/locale/src/macros.rs b/server/locale/src/macros.rs new file mode 100644 index 00000000..1eee1425 --- /dev/null +++ b/server/locale/src/macros.rs @@ -0,0 +1,51 @@ +#[macro_export] +macro_rules! trm { + ($id:literal $(, $typ:ident = $param:expr)*) => { + hurrycurry_protocol::Message::Translation { + id: $id.to_owned(), + params: vec![$($crate::trm_param!($typ, $param)),*] + } + }; +} + +#[macro_export] +macro_rules! trm_param { + (s, $x:expr) => { + hurrycurry_protocol::Message::Text($x) + }; + (i, $x:expr) => { + hurrycurry_protocol::Message::Item($x) + }; + (t, $x:expr) => { + hurrycurry_protocol::Message::Tile($x) + }; + (m, $x:expr) => { + $x + }; +} + +#[macro_export] +macro_rules! tre { + ($id:literal $(, $typ:ident = $param:expr)*) => { + $crate::TrError::Tr { + id: $id, + params: vec![$($crate::tre_param!($typ, $param)),*] + } + }; +} + +#[macro_export] +macro_rules! tre_param { + (s, $x:expr) => { + hurrycurry_protocol::Message::Text($x) + }; + (i, $x:expr) => { + hurrycurry_protocol::Message::Item($x) + }; + (t, $x:expr) => { + hurrycurry_protocol::Message::Tile($x) + }; + (m, $x:expr) => { + $x + }; +} diff --git a/server/src/main.rs b/server/src/main.rs index 0236ebe4..727ad4b1 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -99,44 +99,7 @@ fn main() -> Result<()> { let data_path = if let Some(d) = args.data_dir.clone() { d } else { - let mut test_order = Vec::new(); - - if let Ok(path) = var("HURRYCURRY_DATA_PATH") { - test_order.push(path); - } - - if let Some(path) = option_env!("HURRYCURRY_DATA_PATH") { - test_order.push(path.to_owned()); - } - - #[cfg(debug_assertions)] - test_order.push("data".to_string()); - - #[cfg(windows)] - match read_windows_reg_datadir() { - Ok(path) => test_order.push(path), - Err(e) => warn!("Cannot find read datadir from windows registry: {e}"), - }; - - #[cfg(not(windows))] - test_order.extend([ - "/usr/local/share/hurrycurry/data".to_string(), - "/usr/share/hurrycurry/data".to_string(), - "/opt/hurrycurry/data".to_string(), - ]); - - let Some(d) = test_order - .iter() - .find(|p| PathBuf::from_str(p).unwrap().join("index.yaml").exists()) - else { - warn!("The following paths were tested without success: {test_order:#?}",); - bail!( - "Could not find the data directory. Use the --data-dir option to specify a path." - ); - }; - - info!("Selected data dir {d:?}"); - PathBuf::from_str(d)? + find_data_path()? }; tokio::runtime::Builder::new_multi_thread() @@ -308,6 +271,45 @@ async fn send_packet( } } +fn find_data_path() -> Result<PathBuf> { + let mut test_order = Vec::new(); + + if let Ok(path) = var("HURRYCURRY_DATA_PATH") { + test_order.push(path); + } + + if let Some(path) = option_env!("HURRYCURRY_DATA_PATH") { + test_order.push(path.to_owned()); + } + + #[cfg(debug_assertions)] + test_order.push("data".to_string()); + + #[cfg(windows)] + match read_windows_reg_datadir() { + Ok(path) => test_order.push(path), + Err(e) => warn!("Cannot find read datadir from windows registry: {e}"), + }; + + #[cfg(not(windows))] + test_order.extend([ + "/usr/local/share/hurrycurry/data".to_string(), + "/usr/share/hurrycurry/data".to_string(), + "/opt/hurrycurry/data".to_string(), + ]); + + let Some(d) = test_order + .iter() + .find(|p| PathBuf::from_str(p).unwrap().join("index.yaml").exists()) + else { + warn!("The following paths were tested without success: {test_order:#?}",); + bail!("Could not find the data directory. Use the --data-dir option to specify a path."); + }; + + info!("Selected data dir {d:?}"); + Ok(PathBuf::from_str(d)?) +} + #[cfg(test)] mod test { use hurrycurry_protocol::{Character, PacketS, PlayerClass, PlayerID}; diff --git a/server/src/server.rs b/server/src/server.rs index 267b3062..e3bcd63d 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -25,7 +25,7 @@ use anyhow::{Context, Result}; use hurrycurry_data::{Serverdata, index::DataIndex}; use hurrycurry_game_core::{Game, Involvement, Item, Player, Tile}; use hurrycurry_locale::{ - FALLBACK_LOCALE, TrError, + GLOBAL_LOCALE, TrError, message::{COLORED, MessageDisplayExt}, tre, }; @@ -573,7 +573,7 @@ impl Server { return Ok(()); }; if let Some(message) = &message { - let body = message.display_message(&FALLBACK_LOCALE, &self.game.data, &COLORED); + let body = message.display_message(&GLOBAL_LOCALE, &self.game.data, &COLORED); if !player_data.name.is_empty() { info!("[{player} {:?}] {body}", player_data.name); } else { diff --git a/server/tools/src/map_linter.rs b/server/tools/src/map_linter.rs index 80eae8fa..1631eae6 100644 --- a/server/tools/src/map_linter.rs +++ b/server/tools/src/map_linter.rs @@ -19,7 +19,7 @@ use anyhow::Result; use hurrycurry_data::{Serverdata, index::DataIndex}; use hurrycurry_locale::{ - FALLBACK_LOCALE, + GLOBAL_LOCALE, message::{COLORED, MessageDisplayExt}, trm, }; @@ -146,7 +146,7 @@ static TILE_MODE: LazyLock<HashMap<String, TileMode>> = LazyLock::new(|| { pub fn check_map(map: &str) -> Result<()> { let style = &COLORED; - let locale = &*FALLBACK_LOCALE; + let locale = &*GLOBAL_LOCALE; let index = DataIndex::new("data".into())?; let (data, serverdata) = index.generate(map)?; |