bevy_easy_localize/
lib.rsuse std::future::Future;
use bevy::{
asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},
prelude::*,
reflect::TypePath,
utils::{ConditionalSendFuture, HashMap},
};
pub struct LocalizePlugin;
impl Plugin for LocalizePlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app
.register_asset_loader(TranslationsAssetLoader)
.init_asset::<Translation>()
.add_systems(Update, update);
}
}
#[derive(Resource)]
pub struct Localize {
is_initialized: bool,
set_language_after_init:Option<String>,
current_language_id: usize,
languages: HashMap<String, usize>,
words: HashMap<String, Vec<String>>,
asset_handle_path: Option<String>,
asset_handle: Option<Handle<Translation>>,
}
impl Localize {
pub fn empty() -> Self {
Self {
is_initialized: false,
set_language_after_init:None,
current_language_id: 0,
languages: HashMap::new(),
words: HashMap::new(),
asset_handle_path: None,
asset_handle: None,
}
}
pub fn from_data(translations: &str) -> Self {
let mut localize = Self::empty();
localize.set_data(translations);
localize
}
pub fn from_asset_path(path: &str) -> Self {
let mut localize = Self::empty();
localize.asset_handle_path = Some(path.to_string());
localize
}
pub fn with_default_language(mut self, language:impl ToString) -> Self{
self.set_language(language);
self
}
pub fn set_data(&mut self, translations: &str) {
let mut languages = HashMap::new();
let mut words = HashMap::new();
let mut data = csv::Reader::from_reader(translations.as_bytes());
let mut records: Vec<Vec<_>> = Vec::new();
if let Ok(headers) = data.headers() {
records.push(headers.iter().map(|field| field.to_string()).collect());
}
for result in data.records() {
if let Ok(record) = result {
records.push(record.iter().map(|field| field.to_string()).collect());
}
}
for (language_id, language) in records[0][2..].into_iter().enumerate() {
languages.insert(language.to_string(), language_id);
}
for record in &records[1..] {
let keyword = &record[0];
let translations = record[2..].into_iter().map(|x| x.to_string()).collect();
words.insert(keyword.to_string(), translations);
}
self.languages = languages;
self.words = words;
self.initialized();
}
pub fn get(&self, keyword: &str) -> &str {
match self.words.get(keyword) {
Some(k) => {
if self.current_language_id < k.len() {
&k[self.current_language_id]
} else {
""
}
}
None => "",
}
}
pub fn set_language(&mut self, language: impl ToString){
let language = language.to_string();
if self.is_initialized{
if let Some(language_id) = self.languages.get(&language) {
self.current_language_id = *language_id;
}
else {
error!("Language not found! ({})", language);
}
}
else{
self.set_language_after_init = Some(language);
}
}
fn initialized(&mut self){
self.is_initialized = true;
if let Some(language) = self.set_language_after_init.clone(){
self.set_language(language);
}
}
}
#[derive(Component)]
pub struct LocalizeText {
sections: Vec<String>,
translated_language: Option<usize>,
}
impl LocalizeText {
pub fn from_section(keyword: impl Into<String>) -> Self {
Self {
sections: vec![keyword.into()],
translated_language: None,
}
}
pub fn from_sections(keywords: impl IntoIterator<Item = String>) -> Self {
Self {
sections: keywords.into_iter().collect(),
translated_language: None,
}
}
}
fn update(
localize: Option<ResMut<Localize>>,
translation_assets: ResMut<Assets<Translation>>,
mut ev_asset: EventReader<AssetEvent<Translation>>,
asset_server: Res<AssetServer>,
mut text: Query<(&mut Text, &mut LocalizeText)>,
) {
if let Some(mut localize) = localize {
if let Some(asset_handle_path) = localize.asset_handle_path.clone() {
localize.asset_handle_path = None;
localize.asset_handle = Some(asset_server.load(asset_handle_path));
}
if let Some(asset_handle) = localize.asset_handle.clone() {
for ev in ev_asset.read() {
match ev {
AssetEvent::Added { id } | AssetEvent::Modified { id } => {
if id == &asset_handle.id() {
let translation = translation_assets.get(&asset_handle).unwrap();
localize.set_data(&translation.0);
}
}
_ => {}
}
}
}
if localize.is_initialized {
for (mut text, mut localize_text) in &mut text {
if localize_text.translated_language.is_none()
|| localize_text.translated_language.unwrap_or(0)
!= localize.current_language_id
{
localize_text.translated_language = Some(localize.current_language_id);
for (id, keyword) in localize_text.sections.iter().enumerate() {
text.sections[id].value = localize.get(&keyword).to_string();
}
}
}
}
}
}
#[derive(Asset, TypePath, Debug)]
pub struct Translation(pub String);
#[derive(Default)]
struct TranslationsAssetLoader;
impl AssetLoader for TranslationsAssetLoader {
type Asset = Translation;
type Settings = ();
type Error = std::io::Error;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_: &'a Self::Settings,
_: &'a mut LoadContext,
) -> impl ConditionalSendFuture + Future<Output = Result<<Self as AssetLoader>::Asset, <Self as AssetLoader>::Error>> {
Box::pin(async move {
let mut bytes:Vec<u8> = Vec::new();
reader.read_to_end(&mut bytes).await?;
let translation_asset = Translation(std::str::from_utf8(&bytes).unwrap().to_string());
Ok(translation_asset)
})
}
fn extensions(&self) -> &[&str] {
&["csv"]
}
}