Skip to main content

es_fluent_shared/resource/
spec.rs

1use crate::LanguageIdentifier;
2use crate::fluent::FluentDomain;
3use crate::namespace::ResolvedNamespace;
4use crate::registry::StaticFluentDomain;
5
6use super::{LocaleRelativeFtlPath, ResourceKey, ResourcePlanError};
7
8/// Canonical description of a single localized resource file.
9#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)]
10pub struct ModuleResourceSpec {
11    /// Stable resource key used by managers (e.g., `my-crate`, `my-crate/ui`, `my-crate/ui/button`).
12    pub key: ResourceKey,
13    /// Path under a locale root (e.g., `my-crate.ftl`, `my-crate/ui.ftl`, `my-crate/ui/button.ftl`).
14    pub locale_relative_path: LocaleRelativeFtlPath,
15    /// Whether this resource is required for locale readiness.
16    pub required: bool,
17}
18
19impl ModuleResourceSpec {
20    /// Validates and creates a resource specification.
21    pub fn try_new(
22        key: impl Into<String>,
23        locale_relative_path: impl Into<String>,
24        required: bool,
25    ) -> Result<Self, ResourcePlanError> {
26        let key = key.into();
27        let locale_relative_path = locale_relative_path.into();
28        Ok(Self {
29            key: ResourceKey::try_new(key.clone())
30                .map_err(|details| ResourcePlanError::InvalidResourceKey { key, details })?,
31            locale_relative_path: LocaleRelativeFtlPath::try_new(locale_relative_path.clone())
32                .map_err(|details| ResourcePlanError::InvalidResourcePath {
33                    path: locale_relative_path,
34                    details,
35                })?,
36            required,
37        })
38    }
39
40    /// Creates a resource specification from validated parts.
41    pub fn new(
42        key: ResourceKey,
43        locale_relative_path: LocaleRelativeFtlPath,
44        required: bool,
45    ) -> Self {
46        Self {
47            key,
48            locale_relative_path,
49            required,
50        }
51    }
52
53    /// Validates and creates the base domain resource specification.
54    pub fn try_base(domain: &str, required: bool) -> Result<Self, ResourcePlanError> {
55        Self::try_new(domain, format!("{domain}.ftl"), required)
56    }
57
58    /// Creates the base domain resource specification.
59    pub fn base(domain: &str, required: bool) -> Self {
60        let domain =
61            FluentDomain::try_new(domain).expect("base resource domain should be validated");
62        Self::base_for_domain(&domain, required)
63    }
64
65    /// Creates the base domain resource specification from a validated static domain.
66    pub fn base_for_static_domain(domain: StaticFluentDomain, required: bool) -> Self {
67        Self::new(
68            ResourceKey::from_static_domain(domain),
69            LocaleRelativeFtlPath::try_new(format!("{}.ftl", domain.as_str()))
70                .expect("static domain should produce a valid locale-relative FTL path"),
71            required,
72        )
73    }
74
75    pub(super) fn base_for_domain(domain: &FluentDomain, required: bool) -> Self {
76        Self::new(
77            ResourceKey::from_domain(domain),
78            LocaleRelativeFtlPath::try_new(format!("{}.ftl", domain.as_str()))
79                .expect("domain should produce a valid locale-relative FTL path"),
80            required,
81        )
82    }
83
84    /// Validates and creates a namespaced resource specification.
85    pub fn try_namespaced(
86        domain: &str,
87        namespace: &ResolvedNamespace,
88        required: bool,
89    ) -> Result<Self, ResourcePlanError> {
90        Self::try_new(
91            format!("{domain}/{namespace}"),
92            format!("{domain}/{namespace}.ftl"),
93            required,
94        )
95    }
96
97    /// Creates a namespaced resource specification.
98    pub fn namespaced(domain: &str, namespace: &ResolvedNamespace, required: bool) -> Self {
99        let domain =
100            FluentDomain::try_new(domain).expect("namespaced resource domain should be validated");
101        Self::namespaced_for_domain(&domain, namespace, required)
102    }
103
104    /// Creates a namespaced resource specification from validated static metadata.
105    pub fn namespaced_for_static_domain(
106        domain: StaticFluentDomain,
107        namespace: &ResolvedNamespace,
108        required: bool,
109    ) -> Self {
110        Self::new(
111            ResourceKey::from_static_domain_and_namespace(domain, namespace),
112            LocaleRelativeFtlPath::try_new(format!("{}/{namespace}.ftl", domain.as_str())).expect(
113                "static domain and namespace should produce a valid locale-relative FTL path",
114            ),
115            required,
116        )
117    }
118
119    pub(super) fn namespaced_for_domain(
120        domain: &FluentDomain,
121        namespace: &ResolvedNamespace,
122        required: bool,
123    ) -> Self {
124        Self::new(
125            ResourceKey::from_domain_and_namespace(domain, namespace),
126            LocaleRelativeFtlPath::try_new(format!("{}/{namespace}.ftl", domain.as_str()))
127                .expect("domain and namespace should produce a valid locale-relative FTL path"),
128            required,
129        )
130    }
131
132    /// Returns the full path for a locale (e.g., `en/my-crate.ftl`).
133    pub fn locale_path(&self, lang: &LanguageIdentifier) -> String {
134        format!("{}/{}", lang, self.locale_relative_path)
135    }
136}