interly-macros 0.1.0

Internalization unified
Documentation
use camino::Utf8PathBuf as PathBuf;
use fluent::FluentResource;
use fluent_syntax::ast::{Entry, Expression, InlineExpression, Message, Pattern, PatternElement};
use unic_langid::LanguageIdentifier;

#[derive(Debug, Clone)]
pub(crate) struct LangInfo {
    pub(crate) messages: Vec<MessageInfo>,
    pub(crate) source: String,
}

#[derive(Debug, Clone)]
pub(crate) struct MessageInfo {
    pub(crate) id: String,
    pub(crate) attrs: Vec<String>,
}

pub(crate) fn extract_messages(
    locales: Vec<(PathBuf, String)>,
) -> Result<Vec<(LanguageIdentifier, LangInfo)>, String> {
    let mut res = vec![];
    for (path, source) in locales {
        let resource = FluentResource::try_new(source.clone()).map_err(|(_, e)| {
            e.iter()
                .map(|e| e.to_string())
                .collect::<Vec<_>>()
                .join("\n")
        })?;

        let mut messages = vec![];

        for e in resource.entries() {
            if let Entry::Message(Message {
                id,
                value: Some(Pattern { elements }),
                ..
            }) = e
            {
                let mut attrs = vec![];
                for e in elements {
                    if let PatternElement::Placeable {
                        expression:
                            Expression::Inline(InlineExpression::VariableReference { id, .. }),
                    } = e
                    {
                        attrs.push(id.name.to_owned());
                    }
                }
                messages.push(MessageInfo {
                    id: id.name.to_owned(),
                    attrs,
                });
            }
        }

        res.push((
            extract_lang(path.clone())?,
            LangInfo {
                messages: messages.clone(),
                source,
            },
        ));
    }

    Ok(res)
}

fn extract_lang(path: PathBuf) -> Result<LanguageIdentifier, String> {
    if !matches!(path.extension(), Some("ftl")) {
        return Err(format!(
            "something went wrong, expected .ftl file, but got \"{path}\""
        ));
    }

    path.file_name()
        .expect("path without file_name")
        .strip_suffix(".ftl")
        .unwrap()
        .parse::<LanguageIdentifier>()
        .map_err(|e| e.to_string())
}