aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-10-24 20:53:08 +0200
committermetamuffin <metamuffin@disroot.org>2025-10-24 20:53:08 +0200
commite67d2d03f9e2d66a24a6b7561146af589e019891 (patch)
treed776ae10122b569f6d8faf36cd01088ce1231afc
parent1e5dc0dee2fed17d6cc5c0e98edbb9b72daa6345 (diff)
downloadhurrycurry-e67d2d03f9e2d66a24a6b7561146af589e019891.tar
hurrycurry-e67d2d03f9e2d66a24a6b7561146af589e019891.tar.bz2
hurrycurry-e67d2d03f9e2d66a24a6b7561146af589e019891.tar.zst
Localize book html export
-rw-r--r--makefile6
-rw-r--r--server/book-export/src/main.rs11
-rw-r--r--server/locale/src/error.rs66
-rw-r--r--server/locale/src/lib.rs148
-rw-r--r--server/locale/src/macros.rs51
-rw-r--r--server/src/main.rs78
-rw-r--r--server/src/server.rs4
-rw-r--r--server/tools/src/map_linter.rs4
8 files changed, 207 insertions, 161 deletions
diff --git a/makefile b/makefile
index 723473c8..f09a586f 100644
--- a/makefile
+++ b/makefile
@@ -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)?;