Convert Localization from Asset to Compound

This commit is contained in:
Vincent Foulon 2020-12-29 11:39:12 +01:00
parent 2b6b2fd12e
commit 59651eb032
9 changed files with 123 additions and 78 deletions

View File

@ -659,7 +659,7 @@ impl Hud {
// Load item images.
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
// Load fonts.
let fonts = Fonts::load(&global_state.i18n.fonts, &mut ui)
let fonts = Fonts::load(&global_state.i18n.read().fonts, &mut ui)
.expect("Impossible to load fonts!");
// Get the server name.
let server = &client.server_info().name;
@ -751,7 +751,7 @@ impl Hud {
// FPS
let fps = global_state.clock.stats().average_tps;
let version = common::util::DISPLAY_VERSION_LONG.clone();
let i18n = global_state.i18n.clone();
let i18n = &*global_state.i18n.read();
if self.show.ingame {
let ecs = client.state().ecs();
@ -1260,7 +1260,7 @@ impl Hud {
in_group,
&global_state.settings.gameplay,
self.pulse,
&i18n,
i18n,
&self.imgs,
&self.fonts,
)
@ -1788,7 +1788,7 @@ impl Hud {
global_state,
&self.rot_imgs,
tooltip_manager,
&i18n,
i18n,
&player_stats,
)
.set(self.ids.buttons, ui_widgets)
@ -1810,7 +1810,7 @@ impl Hud {
&self.fonts,
&self.rot_imgs,
tooltip_manager,
&i18n,
i18n,
&player_buffs,
self.pulse,
&global_state,
@ -1830,7 +1830,7 @@ impl Hud {
&self.imgs,
&self.rot_imgs,
&self.fonts,
&i18n,
i18n,
self.pulse,
&global_state,
tooltip_manager,
@ -1847,7 +1847,7 @@ impl Hud {
}
// Popup (waypoint saved and similar notifications)
Popup::new(
&i18n,
i18n,
client,
&self.new_notifications,
&self.fonts,
@ -1951,7 +1951,7 @@ impl Hud {
&self.hotbar,
tooltip_manager,
&mut self.slot_manager,
&i18n,
i18n,
&self.show,
&ability_map,
)
@ -2005,7 +2005,7 @@ impl Hud {
global_state,
&self.imgs,
&self.fonts,
&i18n,
i18n,
)
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
.and_then(self.tab_complete.take(), |c, input| {
@ -2042,7 +2042,7 @@ impl Hud {
&self.show,
&self.imgs,
&self.fonts,
&i18n,
i18n,
fps as f32,
)
.set(self.ids.settings_window, ui_widgets)
@ -2198,7 +2198,7 @@ impl Hud {
client,
&self.imgs,
&self.fonts,
&i18n,
i18n,
info.selected_entity,
&self.rot_imgs,
tooltip_manager,
@ -2226,7 +2226,7 @@ impl Hud {
// Spellbook
if self.show.spell {
match Spell::new(&self.show, client, &self.imgs, &self.fonts, &i18n)
match Spell::new(&self.show, client, &self.imgs, &self.fonts, i18n)
.set(self.ids.spell, ui_widgets)
{
Some(spell::Event::Close) => {
@ -2246,7 +2246,7 @@ impl Hud {
&self.world_map,
&self.fonts,
self.pulse,
&i18n,
i18n,
&global_state,
tooltip_manager,
)
@ -2290,7 +2290,7 @@ impl Hud {
}
if self.show.esc_menu {
match EscMenu::new(&self.imgs, &self.fonts, &i18n).set(self.ids.esc_menu, ui_widgets) {
match EscMenu::new(&self.imgs, &self.fonts, i18n).set(self.ids.esc_menu, ui_widgets) {
Some(esc_menu::Event::OpenSettings(tab)) => {
self.show.open_setting_tab(tab);
},

View File

@ -42,6 +42,19 @@ impl Font {
/// Store font metadata
pub type Fonts = HashMap<String, Font>;
/// Raw localization data, expect the strings to not be loaded here
/// However, metadata informations are correct
/// See `Localization` for more info on each attributes
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct RawLocalization {
pub sub_directories: Vec<String>,
pub string_map: HashMap<String, String>,
pub vector_map: HashMap<String, Vec<String>>,
pub convert_utf8_to_ascii: bool,
pub fonts: Fonts,
pub metadata: LanguageMetadata,
}
/// Store internationalization data
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct Localization {
@ -115,7 +128,7 @@ impl Localization {
/// Return the missing keys compared to the reference language
fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
let reference_localization =
init_localization(&i18n_asset_key(REFERENCE_LANG)).unwrap();
Localization::load_expect(&i18n_asset_key(REFERENCE_LANG)).read();
let reference_string_keys: HashSet<_> =
reference_localization.string_map.keys().cloned().collect();
@ -152,10 +165,34 @@ impl Localization {
);
}
}
// Initializes and return a Localization with the given key
// Panics if the Localization cannot be found
// #[track_caller]
// pub fn load_expect(asset_key: &str) -> assets::AssetHandle<Localization> {
// Self::load(asset_key).unwrap_or_else(|err| {
// panic!(
// "Failed loading essential asset: {} (error={:?})",
// asset_key, err
// )
// })
// }
}
impl From<RawLocalization> for Localization {
fn from(raw: RawLocalization) -> Self {
Self {
sub_directories: raw.sub_directories,
string_map: raw.string_map,
vector_map: raw.vector_map,
convert_utf8_to_ascii: raw.convert_utf8_to_ascii,
fonts: raw.fonts,
metadata: raw.metadata
}
}
}
impl assets::Asset for Localization {
type Loader = LocalizationLoader;
impl assets::Asset for RawLocalization {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
@ -165,26 +202,39 @@ impl assets::Asset for LocalizationFragment {
const EXTENSION: &'static str = "ron";
}
pub struct LocalizationLoader;
impl assets::Loader<Localization> for LocalizationLoader {
fn load(content: Cow<[u8]>, ext: &str) -> Result<Localization, assets::BoxedError> {
let mut asked_localization: Localization = assets::RonLoader::load(content, ext)?;
impl assets::Compound for Localization {
fn load<S: assets::source::Source>(cache: &assets::AssetCache<S>, asset_key: &str) -> Result<Self, assets::Error> {
let raw = cache.load::<RawLocalization>(&(asset_key.to_string()+"._root"))?.cloned();
let mut localization = Localization::from(raw);
// walk through files in the folder, collecting localization fragment to merge inside the asked_localization
for localization_asset in cache.load_dir::<LocalizationFragment>(asset_key)?.iter() {
localization.string_map.extend(localization_asset.read().string_map.clone());
localization.vector_map.extend(localization_asset.read().vector_map.clone());
}
// use the localization's subdirectory list to load fragments from there
for sub_directory in localization.sub_directories.iter() {
for localization_asset in cache.load_dir::<LocalizationFragment>(&(asset_key.to_string() + "." + &sub_directory))?.iter() {
localization.string_map.extend(localization_asset.read().string_map.clone());
localization.vector_map.extend(localization_asset.read().vector_map.clone());
}
}
// Update the text if UTF-8 to ASCII conversion is enabled
if asked_localization.convert_utf8_to_ascii {
for value in asked_localization.string_map.values_mut() {
if localization.convert_utf8_to_ascii {
for value in localization.string_map.values_mut() {
*value = deunicode(value);
}
for value in asked_localization.vector_map.values_mut() {
for value in localization.vector_map.values_mut() {
*value = value.iter().map(|s| deunicode(s)).collect();
}
}
asked_localization.metadata.language_name =
deunicode(&asked_localization.metadata.language_name);
localization.metadata.language_name =
deunicode(&localization.metadata.language_name);
Ok(asked_localization)
}
Ok(localization)
}
}
/// Initializes and return a Localization with the given key
@ -222,18 +272,6 @@ pub fn init_localization(asset_key: &str) -> Result<Localization, assets::BoxedE
Ok(asked_localization)
}
/// Initializes and return a Localization with the given key
/// Panics if the Localization cannot be found
#[track_caller]
pub fn init_localization_expect(asset_key: &str) -> Localization {
init_localization(asset_key).unwrap_or_else(|err| {
panic!(
"Failed loading essential asset: {} (error={:?})",
asset_key, err
)
})
}
/// Load all the available languages located in the voxygen asset directory
pub fn list_localizations() -> Vec<LanguageMetadata> {
let mut languages = vec![];
@ -242,7 +280,7 @@ pub fn list_localizations() -> Vec<LanguageMetadata> {
if let Ok(l18n_entry) = l18n_directory {
if let Some(l18n_key) = l18n_entry.file_name().to_str() {
// load the root file of all the subdirectories
if let Ok(localization) = Localization::load(&("voxygen.i18n.".to_string() + l18n_key + "._root")) {
if let Ok(localization) = RawLocalization::load(&("voxygen.i18n.".to_string() + l18n_key + "._root")) {
languages.push(localization.read().metadata.clone());
}
}

View File

@ -53,7 +53,7 @@ pub struct GlobalState {
#[cfg(feature = "singleplayer")]
pub singleplayer: Option<Singleplayer>,
// TODO: redo this so that the watcher doesn't have to exist for reloading to occur
pub i18n: Localization,
pub i18n: AssetHandle<Localization>,
pub clipboard: Option<iced_winit::Clipboard>,
// NOTE: This can be removed from GlobalState if client state behavior is refactored to not
// enter the game before confirmation of successful character load

View File

@ -5,7 +5,7 @@
use veloren_voxygen::{
audio::AudioFrontend,
i18n::{self, i18n_asset_key, init_localization, init_localization_expect, Localization},
i18n::{self, i18n_asset_key, Localization},
logging,
profile::Profile,
run,
@ -158,7 +158,7 @@ fn main() {
let profile = Profile::load();
let i18n = init_localization(&i18n_asset_key(&settings.language.selected_language))
let i18n = Localization::load(&i18n_asset_key(&settings.language.selected_language))
.unwrap_or_else(|error| {
let selected_language = &settings.language.selected_language;
warn!(
@ -167,9 +167,9 @@ fn main() {
"Impossible to load language: change to the default language (English) instead.",
);
settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
init_localization_expect(&i18n_asset_key(&settings.language.selected_language))
Localization::load_expect(&i18n_asset_key(&settings.language.selected_language))
});
i18n.log_missing_entries();
i18n.read().log_missing_entries();
// Create window
let (window, event_loop) = Window::new(&settings).expect("Failed to create window!");

View File

@ -63,7 +63,7 @@ impl PlayState for CharSelectionState {
self.client.borrow_mut().load_character_list();
// Updated localization in case the selected language was changed
self.char_selection_ui.update_language(global_state.i18n.clone());
self.char_selection_ui.update_language(global_state.i18n);
// Set scale mode in case it was change
self.char_selection_ui
.set_scale_mode(global_state.settings.gameplay.ui_scale);
@ -162,7 +162,7 @@ impl PlayState for CharSelectionState {
}
// Tick the client (currently only to keep the connection alive).
let localized_strings = &global_state.i18n;
let localized_strings = &global_state.i18n.read();
match self.client.borrow_mut().tick(
comp::ControllerInputs::default(),

View File

@ -1395,7 +1395,7 @@ impl CharSelectionUi {
let selected_character = global_state.profile.get_selected_character(server_name);
// Load language
let i18n = &global_state.i18n;
let i18n = &global_state.i18n.read();
// TODO: don't add default font twice
let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
@ -1458,8 +1458,8 @@ impl CharSelectionUi {
}
}
pub fn update_language(&mut self, i18n: Localization) {
let i18n = i18n;
pub fn update_language(&mut self, i18n: AssetHandle<Localization>) {
let i18n = i18n.read();
let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
self.ui.clear_fonts(font);
@ -1478,7 +1478,7 @@ impl CharSelectionUi {
// TODO: do we need whole client here or just character list?
pub fn maintain(&mut self, global_state: &mut GlobalState, client: &Client) -> Vec<Event> {
let mut events = Vec::new();
let i18n = &global_state.i18n;
let i18n = &global_state.i18n.read();
let (mut messages, _) = self.ui.maintain(
self.controls

View File

@ -5,7 +5,7 @@ use super::char_selection::CharSelectionState;
#[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer;
use crate::{
i18n::{i18n_asset_key, init_localization, Localization},
i18n::{i18n_asset_key, Localization},
render::Renderer,
settings::Settings,
window::Event,
@ -49,7 +49,7 @@ impl PlayState for MainMenuState {
// Updated localization in case the selected language was changed
self.main_menu_ui
.update_language(global_state.i18n.clone(), &global_state.settings);
.update_language(global_state.i18n, &global_state.settings);
// Set scale mode in case it was change
self.main_menu_ui
.set_scale_mode(global_state.settings.gameplay.ui_scale);
@ -113,7 +113,7 @@ impl PlayState for MainMenuState {
)));
},
Some(InitMsg::Done(Err(err))) => {
let localized_strings = &global_state.i18n;
let localized_strings = global_state.i18n.read();
self.client_init = None;
global_state.info_message = Some({
let err = match err {
@ -261,10 +261,12 @@ impl PlayState for MainMenuState {
MainMenuEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
global_state.i18n = init_localization(&i18n_asset_key(&global_state.settings.language.selected_language)).unwrap();
global_state.i18n.log_missing_entries();
global_state.i18n = Localization::load_expect(
&i18n_asset_key(&global_state.settings.language.selected_language)
);
global_state.i18n.read().log_missing_entries();
self.main_menu_ui
.update_language(global_state.i18n.clone(), &global_state.settings);
.update_language(global_state.i18n, &global_state.settings);
},
#[cfg(feature = "singleplayer")]
MainMenuEvent::StartSingleplayer => {

View File

@ -131,7 +131,7 @@ struct Controls {
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
i18n: Localization,
i18n: AssetHandle<Localization>,
// Voxygen version
version: String,
// Alpha disclaimer
@ -176,7 +176,7 @@ impl Controls {
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
i18n: Localization,
i18n: AssetHandle<Localization>,
settings: &Settings,
) -> Self {
let version = common::util::DISPLAY_VERSION_LONG.clone();
@ -280,7 +280,7 @@ impl Controls {
&self.imgs,
&self.login_info,
error.as_deref(),
&self.i18n,
&self.i18n.read(),
self.is_selecting_language,
self.selected_language_index,
&language_metadatas,
@ -292,7 +292,7 @@ impl Controls {
&self.imgs,
&settings.networking.servers,
self.selected_server_index,
&self.i18n,
&self.i18n.read(),
button_style,
),
Screen::Connecting {
@ -303,7 +303,7 @@ impl Controls {
&self.imgs,
&connection_state,
self.time,
&self.i18n,
&self.i18n.read(),
button_style,
settings.gameplay.loading_tips,
),
@ -481,7 +481,7 @@ pub struct MainMenuUi {
impl<'a> MainMenuUi {
pub fn new(global_state: &mut GlobalState) -> Self {
// Load language
let i18n = &global_state.i18n;
let i18n = &*global_state.i18n.read();
// TODO: don't add default font twice
let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
@ -501,15 +501,16 @@ impl<'a> MainMenuUi {
fonts,
Imgs::load(&mut ui).expect("Failed to load images"),
ui.add_graphic(Graphic::Image(bg_img, None)),
global_state.i18n.clone(),
global_state.i18n,
&global_state.settings,
);
Self { ui, controls }
}
pub fn update_language(&mut self, i18n: Localization, settings: &Settings) {
self.controls.i18n = i18n.clone();
pub fn update_language(&mut self, i18n: AssetHandle<Localization>, settings: &Settings) {
self.controls.i18n = i18n;
let i18n = &*i18n.read();
let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
self.ui.clear_fonts(font);
self.controls.fonts =

View File

@ -2,7 +2,7 @@ use crate::{
audio::sfx::SfxEvent,
ecs::MyEntity,
hud::{DebugInfo, Event as HudEvent, Hud, HudInfo, PressBehavior},
i18n::{i18n_asset_key, init_localization, Localization},
i18n::{i18n_asset_key, Localization},
key_state::KeyState,
menu::char_selection::CharSelectionState,
render::Renderer,
@ -124,7 +124,7 @@ impl SessionState {
let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
global_state.audio.emit_sfx_item(sfx_trigger_item);
let i18n = &global_state.i18n;
let i18n = global_state.i18n.read();
match inv_event {
InventoryUpdateEvent::CollectFailed => {
@ -146,7 +146,7 @@ impl SessionState {
},
client::Event::Disconnect => return Ok(TickAction::Disconnect),
client::Event::DisconnectionNotification(time) => {
let i18n = &global_state.i18n;
let i18n = global_state.i18n.read();
let message = match time {
0 => String::from(i18n.get("hud.chat.goodbye")),
@ -165,6 +165,7 @@ impl SessionState {
"{}: {}",
global_state
.i18n
.read()
.get("main.login.kicked")
.to_string(),
reason
@ -678,6 +679,7 @@ impl PlayState for SessionState {
global_state.info_message = Some(
global_state
.i18n
.read()
.get("common.connection_lost")
.to_owned(),
);
@ -757,11 +759,11 @@ impl PlayState for SessionState {
);
// Look for changes in the localization files
// if global_state.i18n.reloaded() {
// hud_events.push(HudEvent::ChangeLanguage(Box::new(
// global_state.i18n.metadata.clone(),
// )));
// }
if global_state.i18n.reloaded() {
hud_events.push(HudEvent::ChangeLanguage(Box::new(
global_state.i18n.read().metadata.clone(),
)));
}
// Maintain the UI.
for event in hud_events {
@ -1015,9 +1017,11 @@ impl PlayState for SessionState {
HudEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
global_state.i18n = init_localization(&i18n_asset_key(&global_state.settings.language.selected_language)).unwrap();
global_state.i18n.log_missing_entries();
self.hud.update_fonts(&global_state.i18n);
global_state.i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
global_state.i18n.read().log_missing_entries();
self.hud.update_fonts(&global_state.i18n.read());
},
HudEvent::ChangeFullscreenMode(new_fullscreen_settings) => {
global_state