mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add veloren-common-i18n
- Move common::comp::chat::Content to its own place
This commit is contained in:
parent
b4ae34cd7c
commit
9264fe77b1
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -6946,8 +6946,8 @@ dependencies = [
|
||||
"serde",
|
||||
"tracing",
|
||||
"unic-langid",
|
||||
"veloren-common",
|
||||
"veloren-common-assets",
|
||||
"veloren-common-i18n",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6994,6 +6994,7 @@ dependencies = [
|
||||
"vek 0.15.8",
|
||||
"veloren-common-assets",
|
||||
"veloren-common-base",
|
||||
"veloren-common-i18n",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7054,6 +7055,15 @@ dependencies = [
|
||||
"veloren-common-base",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-common-i18n"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-common-net"
|
||||
version = "0.10.0"
|
||||
|
@ -5,6 +5,7 @@ resolver = "2"
|
||||
members = [
|
||||
"common",
|
||||
"common/assets",
|
||||
"common/i18n",
|
||||
"common/base",
|
||||
"common/dynlib",
|
||||
"common/ecs",
|
||||
|
@ -7,8 +7,8 @@ version = "0.13.0"
|
||||
|
||||
[dependencies]
|
||||
# Assets
|
||||
common = {package = "veloren-common", path = "../../common"}
|
||||
common-assets = {package = "veloren-common-assets", path = "../../common/assets"}
|
||||
common-i18n = { package = "veloren-common-i18n", path = "../../common/i18n" }
|
||||
serde = { workspace = true }
|
||||
# Localization
|
||||
unic-langid = { version = "0.9"}
|
||||
|
@ -19,8 +19,8 @@ use std::{borrow::Cow, io};
|
||||
use assets::{source::DirEntry, AssetExt, AssetGuard, AssetHandle, ReloadWatcher, SharedString};
|
||||
use tracing::warn;
|
||||
// Re-export because I don't like prefix
|
||||
use common::comp::{Content, LocalizationArg};
|
||||
use common_assets as assets;
|
||||
use common_i18n::{Content, LocalizationArg};
|
||||
|
||||
// Re-export for argument creation
|
||||
pub use fluent::{fluent_args, FluentValue};
|
||||
|
@ -20,6 +20,7 @@ default = ["simd"]
|
||||
[dependencies]
|
||||
|
||||
common-base = { package = "veloren-common-base", path = "base" }
|
||||
common-i18n = { package = "veloren-common-i18n", path = "i18n" }
|
||||
# inline_tweak = { workspace = true }
|
||||
|
||||
# Serde
|
||||
|
10
common/i18n/Cargo.toml
Normal file
10
common/i18n/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "veloren-common-i18n"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Crate for structs and methods that acknowledge the need for localization of the game"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, features = ["rc"] }
|
||||
hashbrown = { workspace = true }
|
||||
rand = { workspace = true }
|
120
common/i18n/src/lib.rs
Normal file
120
common/i18n/src/lib.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The type to represent generic localization request, to be sent from server
|
||||
/// to client and then localized (or internationalized) there.
|
||||
// TODO: This could be generalised to *any* in-game text, not just chat messages (hence it not being
|
||||
// called `ChatContent`). A few examples:
|
||||
//
|
||||
// - Signposts, both those appearing as overhead messages and those displayed 'in-world' on a shop
|
||||
// sign
|
||||
// - UI elements
|
||||
// - In-game notes/books (we could add a variant that allows structuring complex, novel textual
|
||||
// information as a syntax tree or some other intermediate format that can be localised by the
|
||||
// client)
|
||||
// TODO: We probably want to have this type be able to represent similar things to
|
||||
// `fluent::FluentValue`, such as numeric values, so that they can be properly localised in whatever
|
||||
// manner is required.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Content {
|
||||
/// The content is a plaintext string that should be shown to the user
|
||||
/// verbatim.
|
||||
Plain(String),
|
||||
/// The content is a localizable message with the given arguments.
|
||||
Localized {
|
||||
/// i18n key
|
||||
key: String,
|
||||
/// Pseudorandom seed value that allows frontends to select a
|
||||
/// deterministic (but pseudorandom) localised output
|
||||
#[serde(default = "random_seed")]
|
||||
seed: u16,
|
||||
/// i18n arguments
|
||||
#[serde(default)]
|
||||
args: HashMap<String, LocalizationArg>,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||
impl From<String> for Content {
|
||||
fn from(text: String) -> Self { Self::Plain(text) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||
impl<'a> From<&'a str> for Content {
|
||||
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||
}
|
||||
|
||||
/// A localisation argument for localised content (see [`Content::Localized`]).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum LocalizationArg {
|
||||
/// The localisation argument is itself a section of content.
|
||||
///
|
||||
/// Note that this allows [`Content`] to recursively refer to itself. It may
|
||||
/// be tempting to decide to parameterise everything, having dialogue
|
||||
/// generated with a compact tree. "It's simpler!", you might say. False.
|
||||
/// Over-parameterisation is an anti-pattern that hurts translators. Where
|
||||
/// possible, prefer fewer levels of nesting unless doing so would result
|
||||
/// in an intractably larger number of combinations. See [here] for the
|
||||
/// guidance provided by the docs for `fluent`, the localisation library
|
||||
/// used by clients.
|
||||
///
|
||||
/// [here]: https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-wet-over-dry
|
||||
Content(Content),
|
||||
/// The localisation argument is a natural number
|
||||
Nat(u64),
|
||||
}
|
||||
|
||||
impl From<Content> for LocalizationArg {
|
||||
fn from(content: Content) -> Self { Self::Content(content) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl From<String> for LocalizationArg {
|
||||
fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl<'a> From<&'a str> for LocalizationArg {
|
||||
fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl From<u64> for LocalizationArg {
|
||||
fn from(n: u64) -> Self { Self::Nat(n) }
|
||||
}
|
||||
|
||||
fn random_seed() -> u16 { rand::random() }
|
||||
|
||||
impl Content {
|
||||
pub fn localized(key: impl ToString) -> Self {
|
||||
Self::Localized {
|
||||
key: key.to_string(),
|
||||
seed: random_seed(),
|
||||
args: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn localized_with_args<'a, A: Into<LocalizationArg>>(
|
||||
key: impl ToString,
|
||||
args: impl IntoIterator<Item = (&'a str, A)>,
|
||||
) -> Self {
|
||||
Self::Localized {
|
||||
key: key.to_string(),
|
||||
seed: rand::random(),
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.into()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_plain(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Plain(text) => Some(text.as_str()),
|
||||
Self::Localized { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
@ -19,11 +19,11 @@ pub mod theropod;
|
||||
|
||||
use crate::{
|
||||
assets::{self, Asset},
|
||||
comp::Content,
|
||||
consts::{HUMAN_DENSITY, WATER_DENSITY},
|
||||
make_case_elim,
|
||||
npc::NpcKind,
|
||||
};
|
||||
use common_i18n::Content;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use strum::Display;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{comp::Content, make_case_elim, make_proj_elim};
|
||||
use crate::{make_case_elim, make_proj_elim};
|
||||
use common_i18n::Content;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
comp::{group::Group, BuffKind},
|
||||
uid::Uid,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use common_i18n::Content;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DenseVecStorage};
|
||||
use std::time::{Duration, Instant};
|
||||
@ -179,121 +179,6 @@ impl<G> ChatType<G> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The content of a chat message.
|
||||
// TODO: This could be generalised to *any* in-game text, not just chat messages (hence it not being
|
||||
// called `ChatContent`). A few examples:
|
||||
//
|
||||
// - Signposts, both those appearing as overhead messages and those displayed 'in-world' on a shop
|
||||
// sign
|
||||
// - UI elements
|
||||
// - In-game notes/books (we could add a variant that allows structuring complex, novel textual
|
||||
// information as a syntax tree or some other intermediate format that can be localised by the
|
||||
// client)
|
||||
// TODO: We probably want to have this type be able to represent similar things to
|
||||
// `fluent::FluentValue`, such as numeric values, so that they can be properly localised in whatever
|
||||
// manner is required.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Content {
|
||||
/// The content is a plaintext string that should be shown to the user
|
||||
/// verbatim.
|
||||
Plain(String),
|
||||
/// The content is a localizable message with the given arguments.
|
||||
Localized {
|
||||
/// i18n key
|
||||
key: String,
|
||||
/// Pseudorandom seed value that allows frontends to select a
|
||||
/// deterministic (but pseudorandom) localised output
|
||||
#[serde(default = "random_seed")]
|
||||
seed: u16,
|
||||
/// i18n arguments
|
||||
#[serde(default)]
|
||||
args: HashMap<String, LocalizationArg>,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||
impl From<String> for Content {
|
||||
fn from(text: String) -> Self { Self::Plain(text) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||
impl<'a> From<&'a str> for Content {
|
||||
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||
}
|
||||
|
||||
/// A localisation argument for localised content (see [`Content::Localized`]).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum LocalizationArg {
|
||||
/// The localisation argument is itself a section of content.
|
||||
///
|
||||
/// Note that this allows [`Content`] to recursively refer to itself. It may
|
||||
/// be tempting to decide to parameterise everything, having dialogue
|
||||
/// generated with a compact tree. "It's simpler!", you might say. False.
|
||||
/// Over-parameterisation is an anti-pattern that hurts translators. Where
|
||||
/// possible, prefer fewer levels of nesting unless doing so would
|
||||
/// result in an intractably larger number of combinations. See [here](https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-wet-over-dry) for the
|
||||
/// guidance provided by the docs for `fluent`, the localisation library
|
||||
/// used by clients.
|
||||
Content(Content),
|
||||
/// The localisation argument is a natural number
|
||||
Nat(u64),
|
||||
}
|
||||
|
||||
impl From<Content> for LocalizationArg {
|
||||
fn from(content: Content) -> Self { Self::Content(content) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl From<String> for LocalizationArg {
|
||||
fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl<'a> From<&'a str> for LocalizationArg {
|
||||
fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl From<u64> for LocalizationArg {
|
||||
fn from(n: u64) -> Self { Self::Nat(n) }
|
||||
}
|
||||
|
||||
fn random_seed() -> u16 { rand::random() }
|
||||
|
||||
impl Content {
|
||||
pub fn localized(key: impl ToString) -> Self {
|
||||
Self::Localized {
|
||||
key: key.to_string(),
|
||||
seed: random_seed(),
|
||||
args: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn localized_with_args<'a, A: Into<LocalizationArg>>(
|
||||
key: impl ToString,
|
||||
args: impl IntoIterator<Item = (&'a str, A)>,
|
||||
) -> Self {
|
||||
Self::Localized {
|
||||
key: key.to_string(),
|
||||
seed: rand::random(),
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v.into()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_plain(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Plain(text) => Some(text.as_str()),
|
||||
Self::Localized { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stores chat text, type
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GenericChatMsg<G> {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::Content;
|
||||
use common_i18n::Content;
|
||||
use vek::Vec2;
|
||||
// TODO: Move this to common/src/, it's not a component
|
||||
|
||||
|
@ -62,8 +62,7 @@ pub use self::{
|
||||
},
|
||||
character_state::{CharacterActivity, CharacterState, StateUpdate},
|
||||
chat::{
|
||||
ChatMode, ChatMsg, ChatType, Content, Faction, LocalizationArg, SpeechBubble,
|
||||
SpeechBubbleType, UnresolvedChatMsg,
|
||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||
},
|
||||
combo::Combo,
|
||||
controller::{
|
||||
@ -107,5 +106,6 @@ pub use self::{
|
||||
teleport::Teleporting,
|
||||
visual::{LightAnimation, LightEmitter},
|
||||
};
|
||||
pub use common_i18n::{Content, LocalizationArg};
|
||||
|
||||
pub use health::{Health, HealthChange};
|
||||
|
@ -3,11 +3,8 @@
|
||||
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||
// module in `server`.
|
||||
|
||||
use crate::{
|
||||
character::CharacterId,
|
||||
comp::{dialogue::Subject, Content},
|
||||
util::Dir,
|
||||
};
|
||||
use crate::{character::CharacterId, comp::dialogue::Subject, util::Dir};
|
||||
use common_i18n::Content;
|
||||
use rand::{seq::IteratorRandom, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Component;
|
||||
|
@ -2,12 +2,12 @@ use crate::{
|
||||
comp::{
|
||||
item::{ItemDefinitionId, ItemDefinitionIdOwned},
|
||||
tool::ToolKind,
|
||||
Content,
|
||||
},
|
||||
lottery::LootSpec,
|
||||
make_case_elim,
|
||||
terrain::Block,
|
||||
};
|
||||
use common_i18n::Content;
|
||||
use hashbrown::HashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use num_derive::FromPrimitive;
|
||||
|
@ -1,11 +1,11 @@
|
||||
use super::{BlockKind, SpriteKind};
|
||||
use crate::{
|
||||
assets::{self, AssetExt, AssetHandle, BoxedError, DotVoxAsset},
|
||||
comp::Content,
|
||||
make_case_elim,
|
||||
vol::{BaseVol, ReadVol, SizedVol, WriteVol},
|
||||
volumes::dyna::{Dyna, DynaError},
|
||||
};
|
||||
use common_i18n::Content;
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
use serde::Deserialize;
|
||||
|
Loading…
Reference in New Issue
Block a user