Skip to content

Commit

Permalink
Add custom functions support
Browse files Browse the repository at this point in the history
Fixes #12
  • Loading branch information
RedMser committed Jul 29, 2024
1 parent be8c2b1 commit 9746af2
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 23 deletions.
4 changes: 4 additions & 0 deletions godot/default/localization_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ extends Control
# Create translations for both English and German languages.
func _init():
var tr_filename = load("res://test.en.ftl")
tr_filename.add_function("tester", func(positional, named):
printt(positional, named)
return 123
)
var tr_foldername = load("res://de/german-test.ftl")
var tr_inline = TranslationFluent.new()
tr_inline.locale = "pt_PT"
Expand Down
2 changes: 1 addition & 1 deletion godot/default/test.en.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ HELLO =
[one] You have one unread { -term }.
*[other] You have { $unreadEmails } unread { -term }s.
}
.meta = An attr.
.meta = An attr. { TESTER(123, "abc", name: "yeah!") }
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

0 comments on commit 9746af2

Please sign in to comment.