Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom functions support #42

Merged
merged 2 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ HELLO =
.meta = An attr.
""".replace("\t", " "))

# Define custom functions to use in messages.
# positional is an array, named is a dictionary.
tr_filename.add_function("STRLEN", func(positional, named):
if positional.is_empty():
return 0
return len(str(positional[0]))
)

# Register via TranslationServer.
TranslationServer.add_translation(tr_filename)
TranslationServer.add_translation(tr_foldername)
Expand Down
113 changes: 92 additions & 21 deletions rust/src/fluent/translation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use godot::global::{str_to_var, var_to_str};
use godot::global::Error as GdErr;
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};

use crate::hacks::SyncSendCallable;
use crate::utils::get_single_regex_match;

use super::project_settings::{PROJECT_SETTING_FALLBACK_LOCALE, PROJECT_SETTING_PARSE_ARGS_IN_MESSAGE, PROJECT_SETTING_UNICODE_ISOLATION};
Expand Down Expand Up @@ -106,27 +107,50 @@ impl TranslationFluent {
}
}

fn map_fluent_error(result: &Result<(), Vec<FluentError>>) -> GdErr {
match result {
Ok(_) => GdErr::OK,
Err(errs) => {
// TODO: Just take first error for now...
let err = errs.first();
match err {
Some(FluentError::Overriding { kind, id }) => {
godot_warn!("{} with id {} already exists!", kind, id);
GdErr::ERR_ALREADY_EXISTS
}
Some(FluentError::ParserError(_err)) => {
// TODO: figure out string from err instance via "kind" / "thiserror" derive
GdErr::ERR_PARSE_ERROR
}
Some(FluentError::ResolverError(err)) => {
godot_warn!("{}", err);
GdErr::ERR_CANT_RESOLVE
}
None => GdErr::FAILED
fn map_fluent_error(error: &FluentError) -> GdErr {
match error {
FluentError::Overriding { kind, id } => {
godot_warn!("{} with id {} already exists!", kind, id);
GdErr::ERR_ALREADY_EXISTS
}
FluentError::ParserError(_err) => {
// TODO: figure out string from err instance via "kind" / "thiserror" derive
GdErr::ERR_PARSE_ERROR
}
FluentError::ResolverError(err) => {
godot_warn!("{}", err);
GdErr::ERR_CANT_RESOLVE
}
}
}

fn map_fluent_error_list(errors: &Vec<FluentError>) -> GdErr {
// TODO: Just take first error for now...
let error = errors.first();
match error {
Some(error) => Self::map_fluent_error(error),
None => GdErr::FAILED,
}
}

fn fluent_to_variant(input: &FluentValue) -> Variant {
match input {
FluentValue::String(str) => str.into_godot().to_variant(),
FluentValue::Number(num) => {
// TODO: unsure what the default value for maximum_fraction_digits is, but likely not zero
if let Some(0) = num.options.maximum_fraction_digits {
// int
(num.value as i64).into_godot().to_variant()
} else {
// float
num.value.into_godot().to_variant()
}
},
FluentValue::Custom(_custom) => todo!("Custom FluentValue conversion"),
FluentValue::None => Variant::nil(),
FluentValue::Error => {
godot_error!("Tried to convert FluentValue::Error to a Variant.");
Variant::nil()
}
}
}
Expand Down Expand Up @@ -255,7 +279,10 @@ impl TranslationFluent {
}
let res = res.unwrap();

Self::map_fluent_error(&bundle.add_resource(res))
match bundle.add_resource(res) {
Ok(_) => GdErr::OK,
Err(errors) => Self::map_fluent_error_list(&errors),
}
}

fn create_bundle(&self) -> Result<FluentBundle<FluentResource>, GdErr> {
Expand Down Expand Up @@ -295,4 +322,48 @@ impl TranslationFluent {
}
}
}

#[func]
pub fn add_function(&mut self, name: GString, callable: SyncSendCallable) -> GdErr {
let bundle = match &mut self.bundle {
Some(bundle) => bundle,
None => &mut {
let bundle = self.create_bundle();
match bundle {
Ok(bundle) => {
self.bundle = Some(bundle);
self.bundle.as_mut().unwrap()
},
Err(err) => return err
}
},
};

let name = String::from(name);
let name_upper = name.to_uppercase();
if name != name_upper {
godot_warn!("add_function expects uppercase function names. Registered function as {name_upper}");
}

let add_result = bundle.add_function(&name_upper, move |positional, named| {
// Convert args to variants
let positional_variants = positional.iter()
.map(|value| Self::fluent_to_variant(value))
.collect::<VariantArray>();
let named_variants = named.iter()
.map(|(key, value)| (key, Self::fluent_to_variant(value)))
.collect::<Dictionary>();

// Run the function and convert its result.
let args = varray![positional_variants, named_variants];
let result = callable.callv(args);
let result_variant = Self::variant_to_fluent(result);
result_variant
});

match add_result {
Ok(_) => GdErr::OK,
Err(error) => Self::map_fluent_error(&error),
}
}
}
45 changes: 45 additions & 0 deletions rust/src/hacks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use godot::prelude::*;
use std::ops::{Deref, DerefMut};

use godot::builtin::Callable;

pub struct SyncSendCallable(Callable);

unsafe impl Sync for SyncSendCallable {}
unsafe impl Send for SyncSendCallable {}

impl Deref for SyncSendCallable {
type Target = Callable;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for SyncSendCallable {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl std::fmt::Debug for SyncSendCallable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl GodotConvert for SyncSendCallable {
type Via = Callable;
}

impl FromGodot for SyncSendCallable {
fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
Callable::try_from_godot(via).map(SyncSendCallable)
}
}

impl ToGodot for SyncSendCallable {
fn to_godot(&self) -> Self::Via {
self.0.to_godot()
}
}
3 changes: 2 additions & 1 deletion rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use godot::prelude::*;
use godot::classes::Engine;

pub mod fluent;
pub mod utils;
pub(crate) mod hacks;
pub(crate) mod utils;

struct FluentI18n;

Expand Down
Loading