use alloc::boxed::Box;
use alloc::string::String;
use mf2_catalog::Catalog;
use mf2_runtime::{Formatter, MsgId, NoErrors, Sink};
use crate::TrDyn;
use crate::leptos::catalog;
#[cfg(feature = "mark-fallback-lang")]
use crate::leptos::lang::Lender;
use crate::leptos::state::{self, TextUse};
use crate::tr::{Tr, TrArgs, TrRich};
pub trait Description: Clone + Send + Sync + 'static + private::Sealed {
fn msg_id(&self) -> MsgId;
fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink);
fn into_stored(self) -> Stored;
}
mod private {
pub trait Sealed {}
impl Sealed for super::Tr {}
impl Sealed for super::TrArgs {}
impl Sealed for super::TrRich {}
impl Sealed for super::TrDyn {}
}
impl Description for Tr {
fn msg_id(&self) -> MsgId {
self.id()
}
fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink) {
(*self).write(f, out, &mut NoErrors);
}
fn into_stored(self) -> Stored {
Stored::Tr(self)
}
}
impl Description for TrArgs {
fn msg_id(&self) -> MsgId {
self.id()
}
fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink) {
TrArgs::write(self, f, out, &mut NoErrors);
}
fn into_stored(self) -> Stored {
Stored::Args(Box::new(self))
}
}
impl Description for TrRich {
fn msg_id(&self) -> MsgId {
self.id()
}
fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink) {
TrRich::write(self, f, out, &mut NoErrors);
}
fn into_stored(self) -> Stored {
Stored::Args(Box::new(self.args().clone()))
}
}
impl Description for TrDyn {
fn msg_id(&self) -> MsgId {
self.id()
}
fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink) {
TrDyn::write(self, f, out, &mut NoErrors);
}
fn into_stored(self) -> Stored {
Stored::Dyn(Box::new(self))
}
}
#[derive(Clone)]
#[non_exhaustive]
pub enum Stored {
Tr(Tr),
Args(Box<TrArgs>),
Dyn(Box<TrDyn>),
}
impl core::fmt::Debug for Stored {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Stored::Tr(t) => {
f.write_str("Tr(")?;
core::fmt::Debug::fmt(t, f)?;
}
Stored::Args(a) => {
f.write_str("Args(")?;
core::fmt::Debug::fmt(&**a, f)?;
}
Stored::Dyn(d) => {
f.write_str("Dyn(")?;
core::fmt::Debug::fmt(&**d, f)?;
}
}
f.write_str(")")
}
}
impl Stored {
#[must_use]
pub fn msg_id(&self) -> MsgId {
match self {
Stored::Tr(t) => t.id(),
Stored::Args(a) => a.id(),
Stored::Dyn(d) => d.id(),
}
}
pub(crate) fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink) {
match self {
Stored::Tr(t) => t.write_text(f, out),
Stored::Args(a) => a.write_text(f, out),
Stored::Dyn(d) => d.write_text(f, out),
}
}
pub(crate) fn has_source(&self) -> bool {
let source = |v: &crate::ArgValue| matches!(v, crate::ArgValue::Source(_));
match self {
Stored::Tr(_) => false,
Stored::Args(a) => a.args().iter().any(source),
Stored::Dyn(d) => d.args().iter().any(|(_, v)| source(v)),
}
}
}
impl Description for Stored {
fn msg_id(&self) -> MsgId {
Stored::msg_id(self)
}
fn write_text(&self, f: &Formatter<'_>, out: &mut dyn Sink) {
Stored::write_text(self, f, out);
}
fn into_stored(self) -> Stored {
self
}
}
impl private::Sealed for Stored {}
std::thread_local! {
static SCRATCH: core::cell::RefCell<String> = const { core::cell::RefCell::new(String::new()) };
}
fn with_scratch<R>(body: impl FnOnce(&mut String) -> R) -> R {
let mut buf = SCRATCH.with(|s| {
s.try_borrow_mut()
.map(|mut held| core::mem::take(&mut *held))
.unwrap_or_default()
});
buf.clear();
let out = body(&mut buf);
SCRATCH.with(|s| {
if let Ok(mut slot) = s.try_borrow_mut()
&& slot.capacity() < buf.capacity()
{
*slot = buf;
}
});
out
}
pub(crate) fn with_text<D: Description, R>(
description: &D,
catalog: &Catalog,
use_: TextUse,
body: impl FnOnce(&str) -> R,
) -> R {
format_with(description, catalog, use_, None, None, body)
}
fn format_with<D: Description, R>(
description: &D,
catalog: &Catalog,
use_: TextUse,
registry: Option<&'static mf2_runtime::Registry>,
bidi: Option<mf2_runtime::BidiStrategy>,
body: impl FnOnce(&str) -> R,
) -> R {
let (Some(cx), Some(installed)) = (state::context_for(use_, bidi), state::registry()) else {
return body("");
};
let f = Formatter::new(catalog, registry.unwrap_or(installed), &cx);
match f.simple(description.msg_id()) {
Some(text) => body(text),
None => with_scratch(|buf| {
description.write_text(&f, buf);
body(buf)
}),
}
}
pub(crate) fn with_active_text<D: Description, R>(
description: &D,
use_: TextUse,
body: impl FnOnce(&str) -> R,
) -> R {
match catalog::current() {
Some(cx) => cx.in_zone(|| {
format_with(
description,
cx.catalog(),
use_,
cx.registry(),
cx.bidi(),
body,
)
}),
None => body(""),
}
}
#[cfg(feature = "mark-fallback-lang")]
pub(crate) fn with_marked_text<D: Description, R>(
description: &D,
catalog: &Catalog,
body: impl FnOnce(&str, Option<Lender<'_>>) -> R,
) -> R {
let lender = Lender::of(catalog, description.msg_id());
format_with(
description,
catalog,
TextUse::Displayed,
None,
None,
|text| body(text, lender),
)
}
#[cfg(feature = "mark-fallback-lang")]
pub(crate) fn with_active_marked_text<D: Description, R>(
description: &D,
body: impl FnOnce(&str, Option<Lender<'_>>) -> R,
) -> R {
match catalog::current() {
Some(cx) => {
let lender = Lender::of(cx.catalog(), description.msg_id());
cx.in_zone(|| {
format_with(
description,
cx.catalog(),
TextUse::Displayed,
cx.registry(),
cx.bidi(),
|text| body(text, lender),
)
})
}
None => body("", None),
}
}
#[cfg(feature = "native")]
pub(crate) fn requested(m: &dyn crate::native::store::TextOf, use_: TextUse) -> Option<String> {
#[cfg(not(feature = "ssr"))]
catalog::track_locale();
let cx = catalog::requested()?;
Some(cx.in_zone(|| format_dyn(m, cx.catalog(), use_, cx.registry(), cx.bidi())))
}
#[cfg(feature = "native")]
pub(crate) fn unrequested(m: &dyn crate::native::store::TextOf, use_: TextUse) -> String {
match catalog::current() {
Some(cx) => cx.in_zone(|| format_dyn(m, cx.catalog(), use_, cx.registry(), cx.bidi())),
None => String::new(),
}
}
#[cfg(feature = "native")]
fn format_dyn(
m: &dyn crate::native::store::TextOf,
catalog: &Catalog,
use_: TextUse,
registry: Option<&'static mf2_runtime::Registry>,
bidi: Option<mf2_runtime::BidiStrategy>,
) -> String {
let (Some(cx), Some(installed)) = (state::context_for(use_, bidi), state::registry()) else {
return String::new();
};
let f = Formatter::new(catalog, registry.unwrap_or(installed), &cx);
match f.simple(m.msg_id()) {
Some(text) => String::from(text),
None => with_scratch(|buf| {
m.write_text(&f, buf);
String::from(buf.as_str())
}),
}
}
pub(crate) fn to_string<D: Description>(description: &D, use_: TextUse) -> String {
#[cfg(not(feature = "ssr"))]
catalog::track_locale();
with_active_text(description, use_, |text: &str| String::from(text))
}