use bevy::prelude::*;
use bevy::ui::UiSystems;
use bevy_cobweb::prelude::*;
use fluent_langneg::LanguageIdentifier;
use smallvec::SmallVec;
use crate::prelude::*;
fn relocalize_text(
localizer: Res<TextLocalizer>,
fonts: Res<FontMap>,
mut localized_text: Query<(Entity, &mut LocalizedText)>,
mut writer: TextUiWriter,
)
{
for (entity, mut localized) in localized_text.iter_mut() {
let mut idx = 0;
writer.for_each(entity, |_, _, mut text, mut font, _| {
localized.localize_span(&localizer, &fonts, &mut *text, &mut font.font, idx);
idx += 1;
});
}
}
fn handle_font_refresh(
fonts: Res<FontMap>,
mut localized_text: Query<(Entity, &mut LocalizedText)>,
mut writer: TextUiWriter,
)
{
for (entity, mut localized) in localized_text.iter_mut() {
let mut idx = 0;
writer.for_each_font(entity, |mut font| {
let this_idx = idx;
idx += 1;
let Some(loc_span) = localized.localization_for_span_mut(this_idx) else { return };
loc_span.update_font(&fonts, &mut font.font);
});
}
}
fn handle_new_localized_text(
localizer: Res<TextLocalizer>,
fonts: Res<FontMap>,
mut localized_text: Query<(Entity, &mut LocalizedText), Added<LocalizedText>>,
mut writer: TextUiWriter,
)
{
for (entity, mut localized) in localized_text.iter_mut() {
let mut idx = 0;
writer.for_each(entity, |_, _, mut text, mut font, _| {
let this_idx = idx;
idx += 1;
if text.is_empty() {
return;
}
if let Some(span_loc) = localized.localization_for_span(this_idx) {
if span_loc.lang().is_some() {
return;
}
}
localized.set_localization_for_span(text.as_str(), this_idx);
localized.localize_span(&localizer, &fonts, &mut *text, &mut font.font, this_idx);
});
}
}
pub enum TextLocalizationResult
{
NewLang,
SameLang,
Fail,
}
#[derive(Reflect, Clone, Default, Debug, PartialEq)]
pub struct LocalizedTextspan
{
#[reflect(ignore)]
id: Option<LanguageIdentifier>,
#[reflect(ignore)]
font_backup: Option<Handle<Font>>,
#[reflect(ignore)]
pub template: String,
}
impl LocalizedTextspan
{
pub fn lang(&self) -> &Option<LanguageIdentifier>
{
&self.id
}
pub fn font_backup(&self) -> &Option<Handle<Font>>
{
&self.font_backup
}
pub fn localize(&mut self, localizer: &TextLocalizer, target: &mut String) -> TextLocalizationResult
{
let Some(lang) = localizer.localize(&self.template, target) else { return TextLocalizationResult::Fail };
if self.id.as_ref() == Some(lang) {
return TextLocalizationResult::SameLang;
}
self.id = Some(lang.clone());
TextLocalizationResult::NewLang
}
pub fn set_font_backup(&mut self, backup: Handle<Font>)
{
self.font_backup = Some(backup);
}
pub fn update_font(&mut self, fonts: &FontMap, target: &mut Handle<Font>)
{
if self.font_backup.is_none() {
self.font_backup = Some(target.clone());
}
let Some(lang_id) = &self.id else {
tracing::warn!("failed setting localized font on a text span, the \
span has not been localized yet; current font is {:?}", target.path());
return;
};
let backup = self.font_backup.as_ref().unwrap();
let new_handle = fonts
.get_localized(lang_id, backup.id())
.unwrap_or_else(|| backup.clone());
*target = new_handle;
}
}
#[derive(Component, Reflect, Clone, Debug, PartialEq)]
pub struct LocalizedText
{
#[reflect(ignore, default = "LocalizedText::default_loc")]
localization: SmallVec<[LocalizedTextspan; 1]>,
}
impl LocalizedText
{
pub fn set_localization(&mut self, data: impl AsRef<str>)
{
self.set_localization_for_span(data, 0);
}
pub fn set_localization_for_span(&mut self, data: impl AsRef<str>, span: usize)
{
if self.localization.len() <= span {
self.localization
.resize(span + 1, LocalizedTextspan::default());
}
let localized_span = &mut self.localization[span];
localized_span.template.clear();
localized_span.template.push_str(data.as_ref());
}
pub fn localization(&self) -> &LocalizedTextspan
{
self.localization_for_span(0).unwrap()
}
pub fn localization_mut(&mut self) -> &mut LocalizedTextspan
{
self.localization_for_span_mut(0).unwrap()
}
pub fn localization_for_span(&self, span: usize) -> Option<&LocalizedTextspan>
{
self.localization.get(span)
}
pub fn localization_for_span_mut(&mut self, span: usize) -> Option<&mut LocalizedTextspan>
{
self.localization.get_mut(span)
}
pub fn localize(
&mut self,
localizer: &TextLocalizer,
fonts: &FontMap,
target: &mut String,
font: &mut Handle<Font>,
) -> bool
{
self.localize_span(localizer, fonts, target, font, 0)
}
pub fn localize_span(
&mut self,
localizer: &TextLocalizer,
fonts: &FontMap,
target: &mut String,
font: &mut Handle<Font>,
span: usize,
) -> bool
{
let Some(loc_span) = self.localization_for_span_mut(span) else {
tracing::warn!("tried to localize text span {span} of an entity, but no localization template is \
available for this span");
return false;
};
match loc_span.localize(localizer, target) {
TextLocalizationResult::Fail => {
tracing::warn!("failed localizing {:?} template for text span {span} on an entity",
loc_span.template);
return false;
}
TextLocalizationResult::NewLang => {
loc_span.update_font(fonts, font);
}
TextLocalizationResult::SameLang => (),
}
true
}
fn default_loc() -> SmallVec<[LocalizedTextspan; 1]>
{
SmallVec::from_buf([LocalizedTextspan::default()])
}
}
impl Default for LocalizedText
{
fn default() -> Self
{
Self { localization: Self::default_loc() }
}
}
pub(crate) struct LocalizedTextPlugin;
impl Plugin for LocalizedTextPlugin
{
fn build(&self, app: &mut App)
{
app.register_component_type::<LocalizedText>()
.react(|rc| rc.on_persistent(broadcast::<RelocalizeApp>(), relocalize_text))
.react(|rc| rc.on_persistent(broadcast::<TextLocalizerLoaded>(), relocalize_text))
.react(|rc| rc.on_persistent(broadcast::<FontMapLoaded>(), handle_font_refresh))
.configure_sets(PostUpdate, LocalizationSet::Update.before(UiSystems::Prepare))
.add_systems(PostUpdate, handle_new_localized_text.in_set(LocalizationSet::Update));
}
}