Skip to main content

fluent_typed/
lib.rs

1#![doc = include_str!("../README.md")]
2#[cfg(any(doc, feature = "build"))]
3mod build;
4mod contract;
5mod error;
6pub(crate) mod ftl_refs;
7mod l10n_bundle;
8mod l10n_language_vec;
9mod structured;
10
11pub use contract::{ContractViolation, ElementContract, MessageContract, validate_ftl};
12pub use error::L10nError;
13
14#[cfg(all(test, feature = "build"))]
15mod tests;
16
17#[cfg(any(doc, feature = "build"))]
18pub use build::{
19    BuildError, BuildOptions, FtlOutputOptions, LintLevel, build_from_locales_folder,
20    try_build_from_locales_folder,
21};
22
23/// Internal build-pipeline pieces exposed for the benchmark suite only. Gated
24/// behind the non-default `bench-internals` feature; not a stable API.
25#[cfg(feature = "bench-internals")]
26pub use build::bench_internals;
27
28pub mod prelude {
29    pub use crate::contract::{ContractViolation, ElementContract, MessageContract, validate_ftl};
30    pub use crate::error::L10nError;
31    pub use crate::l10n_bundle::L10nBundle;
32    pub use crate::l10n_language_vec::L10nLanguageVec;
33    pub use crate::structured::{ElementGap, Segment};
34    pub use fluent_bundle::{FluentArgs, FluentValue, types::FluentNumber};
35    #[cfg(feature = "langneg")]
36    pub use icu_locale_core::{LanguageIdentifier, langid};
37
38    /// Parse an `Accept-Language` header into language identifiers, sorted by
39    /// quality (highest first). Unparseable entries are skipped.
40    #[cfg(feature = "langneg")]
41    pub(crate) fn requested_languages(accept_language: &str) -> Vec<LanguageIdentifier> {
42        let mut requested: Vec<(LanguageIdentifier, u16)> = accept_language
43            .split(',')
44            .filter_map(|entry| {
45                let entry = entry.trim();
46                if entry.is_empty() {
47                    return None;
48                }
49                let (tag, quality) = if let Some((tag, params)) = entry.split_once(';') {
50                    let q = params
51                        .trim()
52                        .strip_prefix("q=")
53                        .and_then(|v| v.parse::<f32>().ok())
54                        .unwrap_or(1.0);
55                    (tag.trim(), (q * 1000.0) as u16)
56                } else {
57                    (entry, 1000)
58                };
59                tag.parse::<LanguageIdentifier>()
60                    .ok()
61                    .map(|lid| (lid, quality))
62            })
63            .collect();
64        requested.sort_by_key(|entry| std::cmp::Reverse(entry.1));
65        requested.into_iter().map(|(lid, _)| lid).collect()
66    }
67
68    #[cfg(feature = "langneg")]
69    pub fn negotiate_languages<'a, A>(accept_language: &str, available: &'a [A]) -> A
70    where
71        A: 'a + AsRef<LanguageIdentifier> + PartialEq + Default + Copy,
72    {
73        // Find the first available language whose language subtag matches a requested one
74        for req in requested_languages(accept_language) {
75            for avail in available {
76                if avail.as_ref().language == req.language {
77                    return *avail;
78                }
79            }
80        }
81        A::default()
82    }
83}