youtube-legend-cli 0.4.0

Non-interactive Rust CLI that downloads YouTube subtitles through third-party providers, using a native Unix stdin/stdout interface.
//! The catalogue key type: one variant per user-facing string.
//!
//! Every locale module implements a `text` function whose `match` over
//! [`Message`] is exhaustive. There is deliberately no `_` arm anywhere
//! in this module tree: adding a variant here must fail the build until
//! every compiled locale supplies a translation.

use super::Language;

/// A single user-facing string, identified by role rather than by its
/// English wording.
///
/// Variants that end a sentence carry the whole sentence. Variants
/// named `...Prefix`-style (for example [`Message::ErrInvalidUsage`])
/// carry only the label; the runtime payload is appended by the caller
/// with `write!` so no translation ever has to embed a format
/// placeholder.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Message {
    // -- AppError labels ---------------------------------------------
    /// Label for [`crate::error::AppError::InvalidUsage`].
    ErrInvalidUsage,
    /// Label for [`crate::error::AppError::InvalidInput`].
    ErrInvalidInput,
    /// Whole message for [`crate::error::AppError::StdinEmpty`].
    ErrStdinEmpty,
    /// Label for [`crate::error::AppError::InvalidUrl`].
    ErrInvalidUrl,
    /// Label for [`crate::error::AppError::NoSubtitle`].
    ErrNoSubtitle,
    /// Whole message for [`crate::error::AppError::ProviderUnavailable`].
    ErrProviderUnavailable,
    /// Whole message for [`crate::error::AppError::RateLimited`].
    ErrRateLimited,
    /// Label for [`crate::error::AppError::Timeout`].
    ErrTimeout,
    /// Label for [`crate::error::AppError::Io`].
    ErrIo,
    /// Label for [`crate::error::AppError::Http`].
    ErrHttp,
    /// Label for [`crate::error::AppError::UrlParse`].
    ErrUrlParse,
    /// Label for [`crate::error::AppError::Serde`].
    ErrSerde,
    /// Label for [`crate::error::AppError::Crypto`].
    ErrCrypto,
    /// Label for [`crate::error::AppError::SubtitleTooLarge`].
    ErrSubtitleTooLarge,
    /// Label for [`crate::error::AppError::Internal`].
    ErrInternal,
    /// Label for [`crate::error::AppError::PlayerResponseMissing`].
    ErrPlayerResponseMissing,
    /// Label for [`crate::error::AppError::PlayerResponseTooLarge`].
    ErrPlayerResponseTooLarge,
    /// Whole message for [`crate::error::AppError::CaptionTrackNotFound`].
    ErrCaptionTrackNotFound,
    /// Second half of [`crate::error::AppError::CaptionsAsrOnly`], which
    /// pairs it with [`Message::ErrNoSubtitle`]. It exists because no
    /// combination of the keys already in the catalogue states "the
    /// only tracks published were machine-generated".
    ErrCaptionsAsrOnly,
    /// Label for [`crate::error::AppError::PlayabilityStatusDenied`].
    ErrPlayabilityStatusDenied,
    /// Label for [`crate::error::AppError::LanguageParseError`].
    ErrLanguageParse,
    /// Label for [`crate::error::AppError::SignatureDecipherFailed`].
    ErrSignatureDecipherFailed,
    /// Label for [`crate::error::AppError::TimedtextUpstreamError`].
    ErrTimedtextUpstream,
    /// Label for [`crate::error::AppError::ProviderProtocolError`].
    ErrProviderProtocol,
    /// Label for [`crate::error::AppError::BrowserNotFound`].
    ErrBrowserNotFound,
    /// Label for [`crate::error::AppError::Config`].
    ErrConfig,
    /// Opening half of [`crate::error::AppError::CaptchaChallenge`].
    ErrCaptchaChallengeFrom,
    /// Closing half of [`crate::error::AppError::CaptchaChallenge`].
    ErrCaptchaHumanRequired,
    /// The word `bytes`, used as a unit suffix.
    UnitBytes,
    /// The word `exceeds`, used between an observed size and a cap.
    WordExceeds,
    /// The word `available`, used to label the list of language tags a
    /// video actually publishes. Without it the bracket that follows
    /// "requested language is unavailable" reads as the REQUESTED tag,
    /// which is the opposite of what it holds.
    WordAvailable,

    // -- NoSubtitleReason --------------------------------------------
    /// [`crate::error::NoSubtitleReason::PrivateOrAgeRestricted`].
    ReasonPrivateOrAgeRestricted,
    /// [`crate::error::NoSubtitleReason::NotFound`].
    ReasonNotFound,
    /// [`crate::error::NoSubtitleReason::Gone`].
    ReasonGone,
    /// [`crate::error::NoSubtitleReason::UnavailableForLegalReasons`].
    ReasonUnavailableForLegalReasons,
    /// [`crate::error::NoSubtitleReason::NotPublished`].
    ReasonNotPublished,
    /// [`crate::error::NoSubtitleReason::LanguageUnavailable`].
    ReasonLanguageUnavailable,

    // -- Cli::validate -----------------------------------------------
    /// `--batch` was combined with a positional URL.
    UsageBatchWithUrl,
    /// Neither a URL, a stdin pipe, nor `--batch` was supplied.
    UsageNoUrl,
    /// The positional URL is longer than the accepted maximum.
    UsageUrlTooLong,
    /// `--quiet` was combined with `--verbose`.
    UsageQuietWithVerbose,
    /// `--timeout` was set to zero.
    UsageTimeoutZero,
    /// `--cache-ttl` was set to zero.
    UsageCacheTtlZero,
    /// `--dry-run` was combined with `--batch`.
    UsageDryRunWithBatch,

    // -- load_config -------------------------------------------------
    /// The config file could not be read from disk.
    ConfigCouldNotRead,
    /// The config file is not syntactically valid TOML.
    ConfigNotValidToml,
    /// The config file carries a key the CLI does not know.
    ConfigUnknownKey,
    /// Opening half of the "wrong type" diagnosis.
    ConfigKeyPrefix,
    /// Closing half of the "wrong type" diagnosis.
    ConfigWrongType,
    /// The `format` key holds a value outside `txt` / `srt`.
    ConfigInvalidFormat,
    /// The `provider` key holds a value outside the known providers.
    ConfigInvalidProvider,
    /// The `log_level` key holds an unknown level.
    ConfigInvalidLogLevel,
    /// The `log_format` key holds a value outside `text` / `json`.
    ConfigInvalidLogFormat,
    /// The `color` key holds a value outside `auto` / `always` / `never`.
    ConfigInvalidColor,
    /// The `ui_lang` key holds a locale this build does not carry.
    ConfigInvalidUiLang,

    // -- Language handling -------------------------------------------
    /// The supplied `--lang` value is not a well-formed BCP 47 tag.
    LangInvalidTag,
    /// The supplied `--ui-lang` value is not compiled into this build.
    LangUnsupportedUi,

    // -- Runtime bootstrap -------------------------------------------
    /// The tracing subscriber could not be installed.
    ErrTracingInit,

    // -- Config store ------------------------------------------------
    /// The platform exposes no directory able to hold the config file.
    ConfigDirUnavailable,
    /// `config get` was asked for a key the operator never set.
    ConfigKeyNotSet,
    /// `config set` was asked to take a credential from argv.
    ConfigKeyIsSecret,
    /// `config set` was invoked with neither a value nor `--from-stdin`.
    ConfigSetNeedsValue,
    /// `config unset` was invoked without `--yes` on a key that is set.
    ConfigKeySetNeedsYes,
    /// Pointer to `config list-keys`, appended to an unknown-key error.
    ConfigUnknownKeyHint,
    /// The value supplied for an integer key does not parse.
    ConfigExpectsInteger,
    /// The value supplied for a boolean key does not parse.
    ConfigExpectsBoolean,
}

impl Message {
    /// Render this message in `language`, with no dependency on the
    /// process-wide interface locale.
    ///
    /// This is the deterministic entry point: tests must call it
    /// instead of [`super::t`] so their expectations never depend on
    /// the locale of the machine running them.
    #[must_use]
    pub fn text(self, language: Language) -> &'static str {
        match language {
            Language::En => super::en::text(self),
            Language::PtBr => super::pt_br::text(self),
            #[cfg(feature = "i18n-europe")]
            Language::Es => super::es::text(self),
            #[cfg(feature = "i18n-europe")]
            Language::Fr => super::fr::text(self),
            #[cfg(feature = "i18n-europe")]
            Language::De => super::de::text(self),
            #[cfg(feature = "i18n-europe")]
            Language::It => super::it::text(self),
            #[cfg(feature = "i18n-cjk")]
            Language::ZhHans => super::zh_hans::text(self),
            #[cfg(feature = "i18n-cjk")]
            Language::ZhHant => super::zh_hant::text(self),
            #[cfg(feature = "i18n-cjk")]
            Language::Ja => super::ja::text(self),
            #[cfg(feature = "i18n-cjk")]
            Language::Ko => super::ko::text(self),
            #[cfg(feature = "i18n-rtl")]
            Language::Ar => super::ar::text(self),
            #[cfg(feature = "i18n-rtl")]
            Language::He => super::he::text(self),
        }
    }

    /// Every variant, in declaration order. Used by the parity test and
    /// by any caller that needs to audit catalogue coverage.
    #[must_use]
    pub fn all() -> &'static [Message] {
        &[
            Message::ErrInvalidUsage,
            Message::ErrInvalidInput,
            Message::ErrStdinEmpty,
            Message::ErrInvalidUrl,
            Message::ErrNoSubtitle,
            Message::ErrProviderUnavailable,
            Message::ErrRateLimited,
            Message::ErrTimeout,
            Message::ErrIo,
            Message::ErrHttp,
            Message::ErrUrlParse,
            Message::ErrSerde,
            Message::ErrCrypto,
            Message::ErrSubtitleTooLarge,
            Message::ErrInternal,
            Message::ErrPlayerResponseMissing,
            Message::ErrPlayerResponseTooLarge,
            Message::ErrCaptionTrackNotFound,
            Message::ErrCaptionsAsrOnly,
            Message::ErrPlayabilityStatusDenied,
            Message::ErrLanguageParse,
            Message::ErrSignatureDecipherFailed,
            Message::ErrTimedtextUpstream,
            Message::ErrProviderProtocol,
            Message::ErrBrowserNotFound,
            Message::ErrConfig,
            Message::ErrCaptchaChallengeFrom,
            Message::ErrCaptchaHumanRequired,
            Message::UnitBytes,
            Message::WordExceeds,
            Message::WordAvailable,
            Message::ReasonPrivateOrAgeRestricted,
            Message::ReasonNotFound,
            Message::ReasonGone,
            Message::ReasonUnavailableForLegalReasons,
            Message::ReasonNotPublished,
            Message::ReasonLanguageUnavailable,
            Message::UsageBatchWithUrl,
            Message::UsageNoUrl,
            Message::UsageUrlTooLong,
            Message::UsageQuietWithVerbose,
            Message::UsageTimeoutZero,
            Message::UsageCacheTtlZero,
            Message::UsageDryRunWithBatch,
            Message::ConfigCouldNotRead,
            Message::ConfigNotValidToml,
            Message::ConfigUnknownKey,
            Message::ConfigKeyPrefix,
            Message::ConfigWrongType,
            Message::ConfigInvalidFormat,
            Message::ConfigInvalidProvider,
            Message::ConfigInvalidLogLevel,
            Message::ConfigInvalidLogFormat,
            Message::ConfigInvalidColor,
            Message::ConfigInvalidUiLang,
            Message::LangInvalidTag,
            Message::LangUnsupportedUi,
            Message::ErrTracingInit,
            Message::ConfigDirUnavailable,
            Message::ConfigKeyNotSet,
            Message::ConfigKeyIsSecret,
            Message::ConfigSetNeedsValue,
            Message::ConfigKeySetNeedsYes,
            Message::ConfigUnknownKeyHint,
            Message::ConfigExpectsInteger,
            Message::ConfigExpectsBoolean,
        ]
    }
}