diff --git a/.gitignore b/.gitignore index 581eb75..965b644 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Godot 4+ specific ignores .godot/ +**/bin/** +!**/bin/.gdignore # Temporary directories node_modules/ diff --git a/godot/default/bin/.gdignore b/godot/default/bin/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/godot/default/export_presets.cfg b/godot/default/export_presets.cfg new file mode 100644 index 0000000..b1fea9c --- /dev/null +++ b/godot/default/export_presets.cfg @@ -0,0 +1,65 @@ +[preset.0] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="bin/fluent-translation.exe" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="" +application/product_version="" +application/company_name="" +application/product_name="" +application/file_description="" +application/copyright="" +application/trademarks="" +application/export_angle=0 +application/export_d3d12=0 +application/d3d12_agility_sdk_multiarch=true +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' +$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' +$trigger = New-ScheduledTaskTrigger -Once -At 00:00 +$settings = New-ScheduledTaskSettingsSet +$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings +Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true +Start-ScheduledTask -TaskName godot_remote_debug +while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" +ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue +Remove-Item -Recurse -Force '{temp_dir}'" +fluent/strip_comments=true diff --git a/godot/default/test.en.ftl b/godot/default/test.en.ftl index 70018f1..faf4d7e 100644 --- a/godot/default/test.en.ftl +++ b/godot/default/test.en.ftl @@ -1,4 +1,7 @@ +# this is a comment +## another comment -term = email +### third kind of comment HELLO = { $unreadEmails -> [one] You have one unread { -term }. diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml index b4bf8c6..406652d 100644 --- a/rust/.cargo/config.toml +++ b/rust/.cargo/config.toml @@ -1,2 +1,2 @@ [env] -GODOT4_BIN = { value = "../build/godot.windows.editor.x86_64.console.exe", relative = true } +GODOT4_BIN = { value = "../build/godot.windows.editor.x86_64.exe", relative = true } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2b9ed54..d491226 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -180,8 +180,8 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "godot" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" dependencies = [ "godot-core", "godot-macros", @@ -189,8 +189,8 @@ dependencies = [ [[package]] name = "godot-bindings" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" dependencies = [ "bindgen", "gdextension-api", @@ -200,13 +200,13 @@ dependencies = [ [[package]] name = "godot-cell" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" [[package]] name = "godot-codegen" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" dependencies = [ "godot-bindings", "heck", @@ -218,8 +218,8 @@ dependencies = [ [[package]] name = "godot-core" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" dependencies = [ "glam", "godot-bindings", @@ -230,8 +230,8 @@ dependencies = [ [[package]] name = "godot-ffi" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" dependencies = [ "gensym", "godot-bindings", @@ -254,10 +254,11 @@ dependencies = [ [[package]] name = "godot-macros" -version = "0.1.1" -source = "git+https://github.com/godot-rust/gdext?branch=master#79edae358e224225248f0f6f7ca3727130d22fd5" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#71b869fa7b5a16a0aeb5b67200b6b7995854676c" dependencies = [ "godot-bindings", + "markdown", "proc-macro2", "quote", "venial", @@ -339,6 +340,15 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "markdown" +version = "1.0.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e61c5c85b392273c4d4ea546e6399ace3e3db172ab01b6de8f3d398d1dbd2ec" +dependencies = [ + "unicode-id", +] + [[package]] name = "memchr" version = "2.7.4" @@ -539,6 +549,12 @@ dependencies = [ "tinystr", ] +[[package]] +name = "unicode-id" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1e9de7d..8811662 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -18,6 +18,6 @@ crate-type = ["cdylib"] # Compile this crate to a dynamic C library. constcat = "0.5.0" fluent = { git = "https://github.com/projectfluent/fluent-rs", branch = "main" } fluent-syntax = { git = "https://github.com/projectfluent/fluent-rs", branch = "main" } -godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = ["experimental-threads"] } +godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = ["register-docs", "experimental-threads"] } itertools = "0.13.0" unic-langid = "0.9.4" diff --git a/rust/src/fluent/editor_plugin.rs b/rust/src/fluent/editor_plugin.rs new file mode 100644 index 0000000..5319947 --- /dev/null +++ b/rust/src/fluent/editor_plugin.rs @@ -0,0 +1,25 @@ +use godot::{classes::{EditorPlugin, IEditorPlugin}, prelude::*}; + +use super::FluentExportPlugin; + +#[derive(GodotClass)] +#[class(tool, editor_plugin, init, base=EditorPlugin)] +pub struct FluentEditorPlugin { + export_plugin: Option>, + base: Base, +} + +#[godot_api] +impl IEditorPlugin for FluentEditorPlugin { + fn enter_tree(&mut self) { + let export_plugin = FluentExportPlugin::new_gd(); + self.export_plugin = Some(export_plugin.clone()); + self.base_mut().add_export_plugin(export_plugin); + } + + fn exit_tree(&mut self) { + let export_plugin = self.export_plugin.take(); + self.base_mut().remove_export_plugin(export_plugin); + self.export_plugin = None; + } +} diff --git a/rust/src/fluent/export_plugin.rs b/rust/src/fluent/export_plugin.rs new file mode 100644 index 0000000..2bec5fd --- /dev/null +++ b/rust/src/fluent/export_plugin.rs @@ -0,0 +1,47 @@ +use godot::{classes::{EditorExportPlatform, EditorExportPlugin, IEditorExportPlugin}, prelude::*}; +use constcat::concat as constcat; + +use super::strip_comments; + +const EXPORT_OPTION_PREFIX: &str = "fluent/"; +const EXPORT_OPTION_STRIP_COMMENTS: &str = constcat!(EXPORT_OPTION_PREFIX, "strip_comments"); + +#[derive(GodotClass)] +#[class(base=EditorExportPlugin)] +pub struct FluentExportPlugin { + base: Base, +} + +#[godot_api] +impl IEditorExportPlugin for FluentExportPlugin { + fn init(base: Base) -> Self { + Self { + base, + } + } + + fn get_export_options(&self, _platform: Gd) -> Array { + array![dict! { + "option": dict! { + "name": GString::from(EXPORT_OPTION_STRIP_COMMENTS), + "type": VariantType::BOOL, + }, + "default_value": Variant::from(true), + }] + } + + fn export_file(&mut self, path: GString, _type: GString, _features: PackedStringArray) { + if !path.to_string().to_lowercase().ends_with("ftl") { + return; + } + + if self.base().get_option(StringName::from(EXPORT_OPTION_STRIP_COMMENTS)).booleanize() { + // Strip comments from file + let contents = strip_comments(path.clone()); + let binary = PackedByteArray::from_iter(contents.bytes()); + + self.base_mut().skip(); + self.base_mut().add_file(path, binary, false); + } + } +} diff --git a/rust/src/fluent/extractor_packed_scene.rs b/rust/src/fluent/extractor_packed_scene.rs index 1ba450b..0efc230 100644 --- a/rust/src/fluent/extractor_packed_scene.rs +++ b/rust/src/fluent/extractor_packed_scene.rs @@ -1,8 +1,8 @@ use std::collections::{HashMap, HashSet}; -use godot::engine::node::AutoTranslateMode; -use godot::engine::{ClassDb, RegEx}; -use godot::engine::{resource_loader::CacheMode, ResourceLoader}; +use godot::classes::{ClassDb, RegEx, ResourceLoader}; +use godot::classes::node::AutoTranslateMode; +use godot::classes::resource_loader::CacheMode; use godot::prelude::*; use super::{FluentTranslationParser, MessageGeneration}; diff --git a/rust/src/fluent/generator.rs b/rust/src/fluent/generator.rs index dc658e7..b75e3a5 100644 --- a/rust/src/fluent/generator.rs +++ b/rust/src/fluent/generator.rs @@ -1,11 +1,13 @@ -use godot::{engine::{utilities::error_string, ProjectSettings, RegEx, RegExMatch}, prelude::*}; +use godot::prelude::*; +use godot::classes::{ProjectSettings, RegEx, RegExMatch}; +use godot::global::error_string; use itertools::Itertools; use std::{collections::HashMap, path::PathBuf}; use fluent_syntax::{ast, parser::parse}; use fluent_syntax::serializer::serialize; use crate::utils::{create_or_open_file_for_read_write, get_files_recursive}; -use godot::engine::global::Error as GdErr; +use godot::global::Error as GdErr; use super::{project_settings::{INVALID_MESSAGE_HANDLING_SKIP, PROJECT_SETTING_GENERATOR_INVALID_MESSAGE_HANDLING, PROJECT_SETTING_GENERATOR_LOCALES, PROJECT_SETTING_GENERATOR_PATTERNS}, FluentPackedSceneTranslationParser, FluentTranslationParser}; diff --git a/rust/src/fluent/global.rs b/rust/src/fluent/global.rs index 66d6c80..071bd49 100644 --- a/rust/src/fluent/global.rs +++ b/rust/src/fluent/global.rs @@ -1,4 +1,5 @@ -use godot::{engine::ResourceLoader, prelude::*}; +use godot::prelude::*; +use godot::classes::ResourceLoader; use super::ResourceFormatLoaderFluent; @@ -12,10 +13,10 @@ impl FluentI18nSingleton { pub(crate) const SINGLETON_NAME: &'static str = "FluentI18nSingleton"; pub(crate) fn register(&self) { - ResourceLoader::singleton().add_resource_format_loader(self.loader.clone().upcast()); + ResourceLoader::singleton().add_resource_format_loader(self.loader.clone()); } pub(crate) fn unregister(&self) { - ResourceLoader::singleton().remove_resource_format_loader(self.loader.clone().upcast()); + ResourceLoader::singleton().remove_resource_format_loader(self.loader.clone()); } } diff --git a/rust/src/fluent/importer.rs b/rust/src/fluent/importer.rs index a47bdd4..887963c 100644 --- a/rust/src/fluent/importer.rs +++ b/rust/src/fluent/importer.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; -use godot::{engine::{FileAccess, IResourceFormatLoader, ProjectSettings, RegEx, ResourceFormatLoader}, prelude::*}; -use godot::engine::global::Error as GdErr; +use godot::prelude::*; +use godot::classes::{FileAccess, IResourceFormatLoader, ProjectSettings, RegEx, ResourceFormatLoader}; +use godot::global::Error as GdErr; use super::{locale::{compute_locale, compute_message_pattern}, project_settings::*, TranslationFluent}; diff --git a/rust/src/fluent/locale.rs b/rust/src/fluent/locale.rs index d809a34..06ff1f1 100644 --- a/rust/src/fluent/locale.rs +++ b/rust/src/fluent/locale.rs @@ -1,6 +1,6 @@ use std::path::{self, PathBuf}; -use godot::engine::{ProjectSettings, RegEx, RegExMatch}; +use godot::classes::{ProjectSettings, RegEx, RegExMatch}; use godot::prelude::*; use unic_langid::LanguageIdentifier; diff --git a/rust/src/fluent/mod.rs b/rust/src/fluent/mod.rs index 1d7113a..81bd651 100644 --- a/rust/src/fluent/mod.rs +++ b/rust/src/fluent/mod.rs @@ -10,6 +10,11 @@ mod importer; pub use self::importer::*; mod translation; pub use self::translation::*; +mod export_plugin; +pub use self::export_plugin::*; +mod strip_comments; +pub use self::strip_comments::*; pub mod locale; #[allow(dead_code)] pub mod project_settings; +mod editor_plugin; diff --git a/rust/src/fluent/project_settings.rs b/rust/src/fluent/project_settings.rs index 4792fdc..671e66e 100644 --- a/rust/src/fluent/project_settings.rs +++ b/rust/src/fluent/project_settings.rs @@ -1,6 +1,6 @@ -use godot::engine::global::PropertyHint; +use godot::global::PropertyHint; use godot::prelude::*; -use godot::engine::ProjectSettings; +use godot::classes::ProjectSettings; use constcat::concat as constcat; const PROJECT_SETTING_PREFIX: &str = "internationalization/fluent/"; diff --git a/rust/src/fluent/strip_comments.rs b/rust/src/fluent/strip_comments.rs new file mode 100644 index 0000000..44d48cf --- /dev/null +++ b/rust/src/fluent/strip_comments.rs @@ -0,0 +1,25 @@ +use godot::prelude::*; +use fluent_syntax::{ast, parser::parse, serializer::serialize}; +use godot::classes::FileAccess; + +pub fn strip_comments(path: GString) -> String { + let contents = FileAccess::get_file_as_string(path.clone()); + let ftl = parse(contents.to_string()); + let mut ftl = match ftl { + Ok(ftl) => ftl, + Err((ftl, err)) => { + godot_warn!("Error parsing {}: {:?}", path, err); + ftl + }, + }; + + ftl.body.retain(|ast| { + match ast { + ast::Entry::Comment(_) | ast::Entry::GroupComment(_) | ast::Entry::ResourceComment(_) => false, + _ => true, + } + }); + + let contents = serialize(&ftl); + contents +} diff --git a/rust/src/fluent/translation.rs b/rust/src/fluent/translation.rs index 4006b49..103a43b 100644 --- a/rust/src/fluent/translation.rs +++ b/rust/src/fluent/translation.rs @@ -3,10 +3,10 @@ use std::sync::{Arc, RwLock}; use fluent::types::{FluentNumber, FluentNumberOptions}; use fluent::{FluentArgs, FluentBundle, FluentError, FluentResource, FluentValue}; -use godot::engine::utilities::{str_to_var, var_to_str}; use godot::prelude::*; -use godot::engine::{ITranslation, ProjectSettings, RegEx, Translation}; -use godot::engine::global::Error as GdErr; +use godot::classes::{ITranslation, ProjectSettings, RegEx, Translation}; +use godot::global::{str_to_var, var_to_str}; +use godot::global::Error as GdErr; use unic_langid::{LanguageIdentifier, LanguageIdentifierError}; use crate::utils::get_single_regex_match; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index c4d91d4..9836e0c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,6 +1,7 @@ use crate::fluent::FluentI18nSingleton; use fluent::project_settings; -use godot::{engine::Engine, prelude::*}; +use godot::prelude::*; +use godot::classes::Engine; pub mod fluent; pub mod utils; @@ -27,7 +28,7 @@ unsafe impl ExtensionLibrary for FluentI18n { singleton.bind().register(); Engine::singleton() - .register_singleton(FluentI18nSingleton::SINGLETON_NAME.into(), singleton.upcast()); + .register_singleton(FluentI18nSingleton::SINGLETON_NAME.into(), singleton); } } diff --git a/rust/src/utils.rs b/rust/src/utils.rs index e85344d..692fafe 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -1,11 +1,11 @@ use std::fmt::Display; use std::path::PathBuf; -use godot::engine::file_access::ModeFlags; -use godot::engine::utilities::error_string; -use godot::engine::{FileAccess, RegExMatch}; -use godot::{engine::DirAccess, prelude::*}; -use godot::engine::global::Error as GdErr; +use godot::classes::file_access::ModeFlags; +use godot::classes::{DirAccess, FileAccess, RegExMatch}; +use godot::global::error_string; +use godot::prelude::*; +use godot::global::Error as GdErr; pub fn get_files_recursive(path: GString) -> Vec { let da = DirAccess::open(path.clone());