Experimenting with more structured ways to handle command-line input/output in Rust
use fluent_langneg::NegotiationStrategy;

// Publicly re-export the macro itself
pub use fluent_embed_derive::localize;

// Public re-exports of dependencies required at runtime, so macro-generated code
// can find all the dependencies it expects as long as this crate exists
pub use fixed_decimal::{Decimal, FloatPrecision};
pub use icu_locale::{self, langid, LanguageIdentifier};
pub use icu_plurals::{self, PluralRuleType, PluralRules, PluralRulesOptions};
pub use jiff::Timestamp;

pub mod decimal;
pub mod string;
pub mod time;

#[derive(thiserror::Error, Debug)]
pub enum LocalizationError {
    #[error("invalid locale selected")]
    InvalidLocale,
    #[error("unable to write localized output")]
    IO(#[from] std::io::Error),
    #[error("unable to retrieve system locales")]
    RetrievalError(env_preferences::LocaleError),
}

pub trait Localize<W: std::io::Write> {
    // TODO: this should be project-wide and tracked at build time
    const CANONICAL_LOCALE: LanguageIdentifier;

    fn available_locales(&self) -> Vec<LanguageIdentifier>;
    fn message_for_locale(
        &self,
        writer: &mut W,
        locale: &LanguageIdentifier,
    ) -> Result<(), LocalizationError>;

    // TODO: remove this hack
    fn old_locale_version(
        new_locales: Vec<icu_locale_core::LanguageIdentifier>,
    ) -> Vec<icu_locid::LanguageIdentifier> {
        new_locales
            .into_iter()
            // Convert new `icu_locale::LanguageIdentifier` to canonical string
            .map(|icu_langid| icu_langid.to_string())
            // Convert canonical string to old `icu_locid` for `fluent_langneg`
            .map(|icu_langid| icu_locid::LanguageIdentifier::try_from_bytes(icu_langid.as_bytes()))
            .map(Result::unwrap)
            .collect::<_>()
    }

    fn localize(&self, writer: &mut W) -> Result<(), LocalizationError> {
        let system_locales = env_preferences::get_locales_lossy()
            .map_err(|error| LocalizationError::RetrievalError(error))?;
        // Lossily convert from a Locale to LanguageIdentifier for Fluent language negotiation
        let system_langids = system_locales
            .into_iter()
            .map(|icu_locale| icu_locale.id)
            .collect::<Vec<icu_locale_core::LanguageIdentifier>>();
        let requested_locales = Self::old_locale_version(system_langids);
        let available_locales = Self::old_locale_version(self.available_locales());
        let canonical_locale = icu_locid::LanguageIdentifier::try_from_bytes(
            Self::CANONICAL_LOCALE.to_string().as_bytes(),
        )
        .unwrap();

        let selected_locale = fluent_langneg::negotiate_languages(
            &requested_locales,
            &available_locales,
            Some(&canonical_locale),
            NegotiationStrategy::Matching,
        );
        self.message_for_locale(
            writer,
            &LanguageIdentifier::try_from_str(&selected_locale[0].to_string()).unwrap(),
        )
    }
}