From 01c30868eb9446d924d9e5eb8718df7b7f4cafa1 Mon Sep 17 00:00:00 2001 From: Ada Lovegirls <8178784-adalovegirls@users.noreply.gitlab.com> Date: Sat, 24 Apr 2021 14:39:35 +0000 Subject: [PATCH] Add option to load English string as fallback if string missing --- CHANGELOG.md | 1 + assets/voxygen/i18n/en/hud/hud_settings.ron | 2 + common/src/assets.rs | 1 + voxygen/src/hud/mod.rs | 8 +- voxygen/src/hud/prompt_dialog.rs | 9 +- voxygen/src/hud/settings_window/language.rs | 48 ++++- voxygen/src/hud/settings_window/mod.rs | 2 +- voxygen/src/i18n.rs | 219 +++++++++++++++----- voxygen/src/lib.rs | 6 +- voxygen/src/main.rs | 9 +- voxygen/src/menu/char_selection/mod.rs | 2 +- voxygen/src/menu/char_selection/ui/mod.rs | 13 +- voxygen/src/menu/main/mod.rs | 9 +- voxygen/src/menu/main/ui/mod.rs | 22 +- voxygen/src/session/mod.rs | 2 +- voxygen/src/session/settings_change.rs | 15 +- voxygen/src/settings/language.rs | 2 + 17 files changed, 267 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31335a5a0e..9d88ec26da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Buoyancy is calculated from the difference in density between an entity and surrounding fluid - Drag is now calculated based on physical properties - Terrain chunks are now deflate-compressed when sent over the network. +- Missing translations can be displayed in English. ### Changed diff --git a/assets/voxygen/i18n/en/hud/hud_settings.ron b/assets/voxygen/i18n/en/hud/hud_settings.ron index 7706b81b24..d7c5ffc949 100644 --- a/assets/voxygen/i18n/en/hud/hud_settings.ron +++ b/assets/voxygen/i18n/en/hud/hud_settings.ron @@ -101,6 +101,8 @@ "hud.settings.audio_device": "Audio Device", "hud.settings.reset_sound": "Reset to Defaults", + "hud.settings.english_fallback": "Display English for missing translations", + "hud.settings.awaitingkey": "Press a key...", "hud.settings.unbound": "None", "hud.settings.reset_keybinds": "Reset to Defaults", diff --git a/common/src/assets.rs b/common/src/assets.rs index cf016cdca2..737810da46 100644 --- a/common/src/assets.rs +++ b/common/src/assets.rs @@ -26,6 +26,7 @@ lazy_static! { pub fn start_hot_reloading() { ASSETS.enhance_hot_reloading(); } pub type AssetHandle = assets_manager::Handle<'static, T>; +pub type AssetGuard = assets_manager::AssetGuard<'static, T>; pub type AssetDir = assets_manager::DirReader<'static, T, source::FileSystem>; /// The Asset trait, which is implemented by all structures that have their data diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index a08f66aa5a..71fa2d015c 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -797,7 +797,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.read().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; @@ -889,7 +889,7 @@ impl Hud { } pub fn update_fonts(&mut self, i18n: &Localization) { - self.fonts = Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!"); + self.fonts = Fonts::load(i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!"); } #[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587 @@ -913,7 +913,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.read(); + let i18n = &global_state.i18n.read(); let key_layout = &global_state.window.key_layout; if self.show.ingame { @@ -2438,7 +2438,7 @@ impl Hud { client, &self.imgs, &self.fonts, - i18n, + &*i18n, self.pulse, &self.rot_imgs, item_tooltip_manager, diff --git a/voxygen/src/hud/prompt_dialog.rs b/voxygen/src/hud/prompt_dialog.rs index 7886f8b5eb..409495d53d 100644 --- a/voxygen/src/hud/prompt_dialog.rs +++ b/voxygen/src/hud/prompt_dialog.rs @@ -1,11 +1,10 @@ use super::{img_ids::Imgs, TEXT_COLOR, UI_HIGHLIGHT_0}; use crate::{ hud::{Event, PromptDialogSettings}, - i18n::Localization, + i18n::LocalizationHandle, settings::Settings, ui::fonts::Fonts, window::GameInput, - AssetHandle, }; use conrod_core::{ widget::{self, Button, Image, Text}, @@ -32,7 +31,7 @@ pub struct PromptDialog<'a> { fonts: &'a Fonts, #[conrod(common_builder)] common: widget::CommonBuilder, - localized_strings: &'a AssetHandle, + localized_strings: &'a LocalizationHandle, settings: &'a Settings, prompt_dialog_settings: &'a PromptDialogSettings, key_layout: &'a Option, @@ -43,7 +42,7 @@ impl<'a> PromptDialog<'a> { pub fn new( imgs: &'a Imgs, fonts: &'a Fonts, - localized_strings: &'a AssetHandle, + localized_strings: &'a LocalizationHandle, settings: &'a Settings, prompt_dialog_settings: &'a PromptDialogSettings, key_layout: &'a Option, @@ -85,7 +84,7 @@ impl<'a> Widget for PromptDialog<'a> { fn update(self, args: widget::UpdateArgs) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; - let _localized_strings = self.localized_strings; + let _localized_strings = &self.localized_strings; let mut event: Option = None; let accept_key = self diff --git a/voxygen/src/hud/settings_window/language.rs b/voxygen/src/hud/settings_window/language.rs index 9117124889..285a70df19 100644 --- a/voxygen/src/hud/settings_window/language.rs +++ b/voxygen/src/hud/settings_window/language.rs @@ -1,13 +1,13 @@ use crate::{ hud::{img_ids::Imgs, TEXT_COLOR}, - i18n::list_localizations, + i18n::{list_localizations, Localization}, session::settings_change::{Language as LanguageChange, Language::*}, - ui::fonts::Fonts, + ui::{fonts::Fonts, ToggleButton}, GlobalState, }; use conrod_core::{ color, - widget::{self, Button, Rectangle, Scrollbar}, + widget::{self, Button, Rectangle, Scrollbar, Text}, widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; @@ -15,6 +15,8 @@ widget_ids! { struct Ids { window, window_r, + english_fallback_button, + english_fallback_button_label, window_scrollbar, language_list[], } @@ -23,15 +25,22 @@ widget_ids! { #[derive(WidgetCommon)] pub struct Language<'a> { global_state: &'a GlobalState, + localized_strings: &'a Localization, imgs: &'a Imgs, fonts: &'a Fonts, #[conrod(common_builder)] common: widget::CommonBuilder, } impl<'a> Language<'a> { - pub fn new(global_state: &'a GlobalState, imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + pub fn new( + global_state: &'a GlobalState, + imgs: &'a Imgs, + fonts: &'a Fonts, + localized_strings: &'a Localization, + ) -> Self { Self { global_state, + localized_strings, imgs, fonts, common: widget::CommonBuilder::default(), @@ -79,6 +88,7 @@ impl<'a> Widget for Language<'a> { // List available languages let selected_language = &self.global_state.settings.language.selected_language; + let english_fallback = self.global_state.settings.language.use_english_fallback; let language_list = list_localizations(); if state.ids.language_list.len() < language_list.len() { state.update(|state| { @@ -117,6 +127,36 @@ impl<'a> Widget for Language<'a> { } } + // English as fallback language + let show_english_fallback = ToggleButton::new( + english_fallback, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0); + let show_english_fallback = if let Some(id) = state.ids.language_list.last() { + show_english_fallback.down_from(*id, 8.0) + //mid_bottom_with_margin_on(id, -button_h) + } else { + show_english_fallback.mid_top_with_margin_on(state.ids.window, 20.0) + }; + let show_english_fallback = show_english_fallback + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.english_fallback_button, ui); + + if english_fallback != show_english_fallback { + events.push(ToggleEnglishFallback(show_english_fallback)); + } + + Text::new(&self.localized_strings.get("hud.settings.english_fallback")) + .right_from(state.ids.english_fallback_button, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.english_fallback_button) + .color(TEXT_COLOR) + .set(state.ids.english_fallback_button_label, ui); + events } } diff --git a/voxygen/src/hud/settings_window/mod.rs b/voxygen/src/hud/settings_window/mod.rs index 640d01581f..9b6388c514 100644 --- a/voxygen/src/hud/settings_window/mod.rs +++ b/voxygen/src/hud/settings_window/mod.rs @@ -288,7 +288,7 @@ impl<'a> Widget for SettingsWindow<'a> { } }, SettingsTab::Lang => { - for change in language::Language::new(global_state, imgs, fonts) + for change in language::Language::new(global_state, imgs, fonts, localized_strings) .top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0) .wh_of(state.ids.settings_content_align) .set(state.ids.language, ui) diff --git a/voxygen/src/i18n.rs b/voxygen/src/i18n.rs index 6002b810b4..5b04959fd3 100644 --- a/voxygen/src/i18n.rs +++ b/voxygen/src/i18n.rs @@ -1,4 +1,4 @@ -use common::assets::{self, AssetExt}; +use common::assets::{self, AssetExt, AssetGuard, AssetHandle}; use deunicode::deunicode; use hashbrown::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; @@ -45,7 +45,7 @@ pub type Fonts = HashMap; /// Raw localization data, expect the strings to not be loaded here /// However, metadata informations are correct -/// See `Localization` for more info on each attributes +/// See `Language` for more info on each attributes #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct RawLocalization { pub sub_directories: Vec, @@ -58,7 +58,7 @@ pub struct RawLocalization { /// Store internationalization data #[derive(Debug, PartialEq, Serialize, Deserialize)] -pub struct Localization { +struct Language { /// A list of subdirectories to lookup for localization files pub sub_directories: Vec, @@ -83,7 +83,7 @@ pub struct Localization { } /// Store internationalization maps -/// These structs are meant to be merged into a Localization +/// These structs are meant to be merged into a Language #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct LocalizationFragment { /// A map storing the localized texts @@ -97,16 +97,13 @@ pub struct LocalizationFragment { pub vector_map: HashMap>, } -impl Localization { +impl Language { /// Get a localized text from the given key /// /// If the key is not present in the localization object /// then the key is returned. - pub fn get<'a>(&'a self, key: &'a str) -> &str { - match self.string_map.get(key) { - Some(localized_text) => localized_text, - None => key, - } + pub fn get<'a>(&'a self, key: &'a str) -> Option<&str> { + self.string_map.get(key).map(|s| s.as_str()) } /// Get a variation of localized text from the given key @@ -115,55 +112,32 @@ impl Localization { /// /// If the key is not present in the localization object /// then the key is returned. - pub fn get_variation<'a>(&'a self, key: &'a str, index: u16) -> &str { - match self.vector_map.get(key) { - Some(v) if !v.is_empty() => &v[index as usize % v.len()], - _ => key, - } + pub fn get_variation<'a>(&'a self, key: &'a str, index: u16) -> Option<&str> { + self.vector_map + .get(key) + .map(|v| { + if !v.is_empty() { + Some(v[index as usize % v.len()].as_str()) + } else { + None + } + }) + .flatten() } +} - /// Return the missing keys compared to the reference language - fn list_missing_entries(&self) -> (HashSet, HashSet) { - let reference_localization = - Localization::load_expect(&i18n_asset_key(REFERENCE_LANG)).read(); - - let reference_string_keys: HashSet<_> = - reference_localization.string_map.keys().cloned().collect(); - let string_keys: HashSet<_> = self.string_map.keys().cloned().collect(); - let strings = reference_string_keys - .difference(&string_keys) - .cloned() - .collect(); - - let reference_vector_keys: HashSet<_> = - reference_localization.vector_map.keys().cloned().collect(); - let vector_keys: HashSet<_> = self.vector_map.keys().cloned().collect(); - let vectors = reference_vector_keys - .difference(&vector_keys) - .cloned() - .collect(); - - (strings, vectors) - } - - /// Log missing entries (compared to the reference language) as warnings - pub fn log_missing_entries(&self) { - let (missing_strings, missing_vectors) = self.list_missing_entries(); - for missing_key in missing_strings { - warn!( - "[{:?}] Missing string key {:?}", - self.metadata.language_identifier, missing_key - ); - } - for missing_key in missing_vectors { - warn!( - "[{:?}] Missing vector key {:?}", - self.metadata.language_identifier, missing_key - ); +impl Default for Language { + fn default() -> Self { + Self { + sub_directories: Vec::default(), + string_map: HashMap::default(), + vector_map: HashMap::default(), + ..Default::default() } } } -impl From for Localization { + +impl From for Language { fn from(raw: RawLocalization) -> Self { Self { sub_directories: raw.sub_directories, @@ -195,7 +169,7 @@ impl assets::Asset for LocalizationFragment { const EXTENSION: &'static str = "ron"; } -impl assets::Compound for Localization { +impl assets::Compound for Language { fn load( cache: &assets::AssetCache, asset_key: &str, @@ -203,7 +177,7 @@ impl assets::Compound for Localization { let raw = cache .load::(&[asset_key, ".", LANG_MANIFEST_FILE].concat())? .cloned(); - let mut localization = Localization::from(raw); + let mut localization = Language::from(raw); // Walk through files in the folder, collecting localization fragment to merge // inside the asked_localization @@ -247,6 +221,139 @@ impl assets::Compound for Localization { } } +/// the central data structure to handle localization in veloren +// inherit Copy+Clone from AssetHandle +#[derive(Debug, PartialEq, Copy, Clone)] +pub struct LocalizationHandle { + active: AssetHandle, + fallback: Option>, + pub use_english_fallback: bool, +} + +// RAII guard returned from Localization::read(), resembles AssetGuard +pub struct LocalizationGuard { + active: AssetGuard, + fallback: Option>, +} + +// arbitrary choice to minimize changing all of veloren +pub type Localization = LocalizationGuard; + +impl LocalizationGuard { + /// Get a localized text from the given key + /// + /// If the key is not present in the localization object + /// then the key is returned. + pub fn get<'a>(&'a self, key: &'a str) -> &str { + self.active.get(key).unwrap_or_else(|| { + self.fallback + .as_ref() + .map(|f| f.get(key)) + .flatten() + .unwrap_or(key) + }) + } + + /// Get a variation of localized text from the given key + /// + /// `index` should be a random number from `0` to `u16::max()` + /// + /// If the key is not present in the localization object + /// then the key is returned. + pub fn get_variation<'a>(&'a self, key: &'a str, index: u16) -> &str { + self.active.get_variation(key, index).unwrap_or_else(|| { + self.fallback + .as_ref() + .map(|f| f.get_variation(key, index)) + .flatten() + .unwrap_or(key) + }) + } + + /// Return the missing keys compared to the reference language + fn list_missing_entries(&self) -> (HashSet, HashSet) { + if let Some(ref_lang) = &self.fallback { + let reference_string_keys: HashSet<_> = ref_lang.string_map.keys().cloned().collect(); + let string_keys: HashSet<_> = self.active.string_map.keys().cloned().collect(); + let strings = reference_string_keys + .difference(&string_keys) + .cloned() + .collect(); + + let reference_vector_keys: HashSet<_> = ref_lang.vector_map.keys().cloned().collect(); + let vector_keys: HashSet<_> = self.active.vector_map.keys().cloned().collect(); + let vectors = reference_vector_keys + .difference(&vector_keys) + .cloned() + .collect(); + + (strings, vectors) + } else { + (HashSet::default(), HashSet::default()) + } + } + + /// Log missing entries (compared to the reference language) as warnings + pub fn log_missing_entries(&self) { + let (missing_strings, missing_vectors) = self.list_missing_entries(); + for missing_key in missing_strings { + warn!( + "[{:?}] Missing string key {:?}", + self.metadata().language_identifier, + missing_key + ); + } + for missing_key in missing_vectors { + warn!( + "[{:?}] Missing vector key {:?}", + self.metadata().language_identifier, + missing_key + ); + } + } + + pub fn fonts(&self) -> &Fonts { &self.active.fonts } + + pub fn metadata(&self) -> &LanguageMetadata { &self.active.metadata } +} + +impl LocalizationHandle { + pub fn set_english_fallback(&mut self, use_english_fallback: bool) { + self.use_english_fallback = use_english_fallback; + } + + pub fn read(&self) -> LocalizationGuard { + LocalizationGuard { + active: self.active.read(), + fallback: if self.use_english_fallback { + self.fallback.map(|f| f.read()) + } else { + None + }, + } + } + + pub fn load(specifier: &str) -> Result { + let default_key = i18n_asset_key(REFERENCE_LANG); + let is_default = specifier == default_key; + Ok(Self { + active: Language::load(specifier)?, + fallback: if is_default { + None + } else { + Language::load(&default_key).ok() + }, + use_english_fallback: false, + }) + } + + pub fn load_expect(specifier: &str) -> Self { + Self::load(specifier).expect("Can't load language files") + } + + pub fn reloaded(&mut self) -> bool { self.active.reloaded() } +} + #[derive(Clone, Debug)] struct LocalizationList(Vec); diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index c5bfd4fe60..81df2ba090 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -40,13 +40,13 @@ pub use crate::error::Error; use crate::singleplayer::Singleplayer; use crate::{ audio::AudioFrontend, - i18n::Localization, + i18n::LocalizationHandle, profile::Profile, render::Renderer, settings::Settings, window::{Event, Window}, }; -use common::{assets::AssetHandle, clock::Clock}; +use common::clock::Clock; use common_base::span; /// A type used to store state that is shared between all play states. @@ -61,7 +61,7 @@ pub struct GlobalState { #[cfg(feature = "singleplayer")] pub singleplayer: Option, // TODO: redo this so that the watcher doesn't have to exist for reloading to occur - pub i18n: AssetHandle, + pub i18n: LocalizationHandle, pub clipboard: Option, // NOTE: This can be removed from GlobalState if client state behavior is refactored to not // enter the game before confirmation of successful character load diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 27003f2251..63ba0d7c48 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -5,7 +5,7 @@ use veloren_voxygen::{ audio::AudioFrontend, - i18n::{self, i18n_asset_key, Localization}, + i18n::{self, i18n_asset_key, LocalizationHandle}, profile::Profile, run, scene::terrain::SpriteRenderContext, @@ -15,7 +15,7 @@ use veloren_voxygen::{ }; use common::{ - assets::{self, AssetExt}, + assets::{self}, clock::Clock, }; use std::panic; @@ -163,7 +163,7 @@ fn main() { // Load the profile. let profile = Profile::load(); - let i18n = Localization::load(&i18n_asset_key(&settings.language.selected_language)) + let mut i18n = LocalizationHandle::load(&i18n_asset_key(&settings.language.selected_language)) .unwrap_or_else(|error| { let selected_language = &settings.language.selected_language; warn!( @@ -172,9 +172,10 @@ fn main() { "Impossible to load language: change to the default language (English) instead.", ); settings.language.selected_language = i18n::REFERENCE_LANG.to_owned(); - Localization::load_expect(&i18n_asset_key(&settings.language.selected_language)) + LocalizationHandle::load_expect(&i18n_asset_key(&settings.language.selected_language)) }); i18n.read().log_missing_entries(); + i18n.set_english_fallback(settings.language.use_english_fallback); // Create window let (mut window, event_loop) = Window::new(&settings).expect("Failed to create window!"); diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index b51f4bfdd1..a7825976a7 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -166,7 +166,7 @@ impl PlayState for CharSelectionState { } // Tick the client (currently only to keep the connection alive). - let localized_strings = &*global_state.i18n.read(); + let localized_strings = &global_state.i18n.read(); match self.client.borrow_mut().tick( comp::ControllerInputs::default(), diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 2a8b8c142d..df028ea997 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -1,5 +1,5 @@ use crate::{ - i18n::Localization, + i18n::{Localization, LocalizationHandle}, render::Renderer, ui::{ self, @@ -22,7 +22,6 @@ use crate::{ }; use client::{Client, ServerInfo}; use common::{ - assets::AssetHandle, character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER, MAX_NAME_LENGTH}, comp::{self, humanoid, inventory::slot::EquipSlot, Inventory, Item}, LoadoutBuilder, @@ -1467,7 +1466,7 @@ impl CharSelectionUi { 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); + let font = ui::ice::load_font(&i18n.fonts().get("cyri").unwrap().asset_key); let mut ui = Ui::new( &mut global_state.window, @@ -1476,7 +1475,7 @@ impl CharSelectionUi { ) .unwrap(); - let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts"); + let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts"); #[cfg(feature = "singleplayer")] let default_name = match global_state.singleplayer { @@ -1538,13 +1537,13 @@ impl CharSelectionUi { } } - pub fn update_language(&mut self, i18n: AssetHandle) { + pub fn update_language(&mut self, i18n: LocalizationHandle) { let i18n = i18n.read(); - let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key); + let font = ui::ice::load_font(&i18n.fonts().get("cyri").unwrap().asset_key); self.ui.clear_fonts(font); self.controls.fonts = - Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!"); + Fonts::load(i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!"); } pub fn set_scale_mode(&mut self, scale_mode: ui::ScaleMode) { diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 76df1f8c19..642d902871 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -5,7 +5,7 @@ use super::char_selection::CharSelectionState; #[cfg(feature = "singleplayer")] use crate::singleplayer::Singleplayer; use crate::{ - i18n::{i18n_asset_key, Localization}, + i18n::{i18n_asset_key, Localization, LocalizationHandle}, render::Renderer, settings::Settings, window::Event, @@ -18,7 +18,7 @@ use client::{ ServerInfo, }; use client_init::{ClientConnArgs, ClientInit, Error as InitError, Msg as InitMsg}; -use common::{assets::AssetExt, comp}; +use common::comp; use common_base::span; use std::{fmt::Debug, sync::Arc}; use tokio::runtime; @@ -295,10 +295,13 @@ impl PlayState for MainMenuState { MainMenuEvent::ChangeLanguage(new_language) => { global_state.settings.language.selected_language = new_language.language_identifier; - global_state.i18n = Localization::load_expect(&i18n_asset_key( + global_state.i18n = LocalizationHandle::load_expect(&i18n_asset_key( &global_state.settings.language.selected_language, )); global_state.i18n.read().log_missing_entries(); + global_state + .i18n + .set_english_fallback(global_state.settings.language.use_english_fallback); self.main_menu_ui .update_language(global_state.i18n, &global_state.settings); }, diff --git a/voxygen/src/menu/main/ui/mod.rs b/voxygen/src/menu/main/ui/mod.rs index 24d04cde1f..063845266e 100644 --- a/voxygen/src/menu/main/ui/mod.rs +++ b/voxygen/src/menu/main/ui/mod.rs @@ -5,7 +5,7 @@ mod login; mod servers; use crate::{ - i18n::{LanguageMetadata, Localization}, + i18n::{LanguageMetadata, LocalizationHandle}, render::Renderer, ui::{ self, @@ -19,7 +19,7 @@ use crate::{ use iced::{text_input, Column, Container, HorizontalAlignment, Length, Row, Space}; //ImageFrame, Tooltip, use crate::settings::Settings; -use common::assets::{self, AssetExt, AssetHandle}; +use common::assets::{self, AssetExt}; use rand::{seq::SliceRandom, thread_rng}; use std::time::Duration; @@ -124,7 +124,7 @@ struct Controls { fonts: Fonts, imgs: Imgs, bg_img: widget::image::Handle, - i18n: AssetHandle, + i18n: LocalizationHandle, // Voxygen version version: String, // Alpha disclaimer @@ -169,7 +169,7 @@ impl Controls { fonts: Fonts, imgs: Imgs, bg_img: widget::image::Handle, - i18n: AssetHandle, + i18n: LocalizationHandle, settings: &Settings, ) -> Self { let version = common::util::DISPLAY_VERSION_LONG.clone(); @@ -480,9 +480,9 @@ pub struct MainMenuUi { impl<'a> MainMenuUi { pub fn new(global_state: &mut GlobalState) -> Self { // Load language - let i18n = &*global_state.i18n.read(); + let i18n = &global_state.i18n.read(); // TODO: don't add default font twice - let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key); + let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key); let mut ui = Ui::new( &mut global_state.window, @@ -491,7 +491,7 @@ impl<'a> MainMenuUi { ) .unwrap(); - let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts"); + let fonts = Fonts::load(&i18n.fonts(), &mut ui).expect("Impossible to load fonts"); let bg_img_spec = BG_IMGS.choose(&mut thread_rng()).unwrap(); @@ -507,13 +507,13 @@ impl<'a> MainMenuUi { Self { ui, controls } } - pub fn update_language(&mut self, i18n: AssetHandle, settings: &Settings) { + pub fn update_language(&mut self, i18n: LocalizationHandle, settings: &Settings) { self.controls.i18n = i18n; - let i18n = &*i18n.read(); - let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key); + let i18n = &i18n.read(); + let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key); self.ui.clear_fonts(font); self.controls.fonts = - Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!"); + Fonts::load(&i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!"); let language_metadatas = crate::i18n::list_localizations(); self.controls.selected_language_index = language_metadatas .iter() diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 102cfd3201..fa1b98732c 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -914,7 +914,7 @@ impl PlayState for SessionState { // Look for changes in the localization files if global_state.i18n.reloaded() { hud_events.push(HudEvent::SettingsChange( - ChangeLanguage(Box::new(global_state.i18n.read().metadata.clone())).into(), + ChangeLanguage(Box::new(global_state.i18n.read().metadata().clone())).into(), )); } diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index d46f1c4c25..3e52b32183 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -5,7 +5,7 @@ use crate::{ BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ScaleChange, ShortcutNumbers, XpBar, }, - i18n::{i18n_asset_key, LanguageMetadata, Localization}, + i18n::{i18n_asset_key, LanguageMetadata, LocalizationHandle}, render::RenderMode, settings::{ AudioSettings, ControlSettings, Fps, GamepadSettings, GameplaySettings, GraphicsSettings, @@ -14,7 +14,6 @@ use crate::{ window::{FullScreenSettings, GameInput}, GlobalState, }; -use common::assets::AssetExt; use vek::*; #[derive(Clone)] @@ -118,6 +117,7 @@ pub enum Interface { #[derive(Clone)] pub enum Language { ChangeLanguage(Box), + ToggleEnglishFallback(bool), } #[derive(Clone)] pub enum Networking {} @@ -470,12 +470,21 @@ impl SettingsChange { SettingsChange::Language(language_change) => match language_change { Language::ChangeLanguage(new_language) => { settings.language.selected_language = new_language.language_identifier; - global_state.i18n = Localization::load_expect(&i18n_asset_key( + global_state.i18n = LocalizationHandle::load_expect(&i18n_asset_key( &settings.language.selected_language, )); global_state.i18n.read().log_missing_entries(); + global_state + .i18n + .set_english_fallback(settings.language.use_english_fallback); session_state.hud.update_fonts(&global_state.i18n.read()); }, + Language::ToggleEnglishFallback(toggle_fallback) => { + settings.language.use_english_fallback = toggle_fallback; + global_state + .i18n + .set_english_fallback(settings.language.use_english_fallback); + }, }, SettingsChange::Networking(networking_change) => match networking_change {}, } diff --git a/voxygen/src/settings/language.rs b/voxygen/src/settings/language.rs index 48427f38a1..b8e3f8b20e 100644 --- a/voxygen/src/settings/language.rs +++ b/voxygen/src/settings/language.rs @@ -5,12 +5,14 @@ use serde::{Deserialize, Serialize}; #[serde(default)] pub struct LanguageSettings { pub selected_language: String, + pub use_english_fallback: bool, } impl Default for LanguageSettings { fn default() -> Self { Self { selected_language: i18n::REFERENCE_LANG.to_string(), + use_english_fallback: true, } } }