pabitell_lib/
translations.rs

1use anyhow::{anyhow, Result};
2use fluent::{bundle::FluentBundle, FluentArgs, FluentResource};
3use include_dir::{include_dir, Dir, DirEntry};
4use intl_memoizer::concurrent::IntlLangMemoizer;
5use lazy_static::lazy_static;
6use std::{
7    collections::HashMap,
8    str::{from_utf8, FromStr},
9};
10use unic_langid::LanguageIdentifier;
11
12pub static RESOURCES: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources/");
13
14lazy_static! {
15    static ref BUNDLES: HashMap<String, FluentBundle<FluentResource, IntlLangMemoizer>> = {
16        let mut res = HashMap::new();
17        for lang in get_available_locales(&RESOURCES).expect("failed to list translations") {
18            match get_bundle(&RESOURCES, lang.clone(), "global") {
19                Err(err) => panic!("failed to load translations: {}", err),
20                Ok(bundle) => {
21                    res.insert(lang.to_string(), bundle);
22                }
23            }
24        }
25        res
26    };
27}
28
29pub fn read_language_data(
30    resoure_dir: &Dir,
31    id: &LanguageIdentifier,
32    translation_name: &str,
33) -> Result<String> {
34    let file = resoure_dir
35        .get_file(format!("{}/{}.ftl", id, translation_name))
36        .ok_or_else(|| anyhow!("'{}' translation not found", translation_name))?;
37    Ok(from_utf8(file.contents())?.to_string())
38}
39
40pub fn get_available_locales(resoure_dir: &Dir) -> Result<Vec<LanguageIdentifier>> {
41    resoure_dir
42        .find("*")
43        .map_err(|err| anyhow!("{}", err))?
44        .filter_map(|e| {
45            if let DirEntry::Dir(dir) = e {
46                Some(
47                    LanguageIdentifier::from_str(dir.path().file_name()?.to_str()?)
48                        .map_err(|err| anyhow!("{}", err)),
49                )
50            } else {
51                None
52            }
53        })
54        .collect()
55}
56
57pub fn get_bundle(
58    resoure_dir: &Dir,
59    lang: LanguageIdentifier,
60    translation_name: &str,
61) -> Result<FluentBundle<FluentResource, IntlLangMemoizer>> {
62    let available = get_available_locales(resoure_dir)?;
63
64    if !available.contains(&lang) {
65        return Err(anyhow!("{} was not found in available languages", lang));
66    }
67
68    let mut bundle = FluentBundle::new_concurrent(vec![lang.clone()]);
69    let data = read_language_data(resoure_dir, &lang, translation_name)?;
70    let resource = FluentResource::try_new(data)
71        .map_err(|_| anyhow!("Failed to parse flt file for language {}", lang))?;
72    bundle.add_resource_overriding(resource);
73
74    Ok(bundle)
75}
76
77pub fn get_message(
78    bundle: &FluentBundle<FluentResource, IntlLangMemoizer>,
79    msgid: &str,
80    args: Option<FluentArgs>,
81) -> Result<String> {
82    let mut errors = vec![];
83    let msg = bundle
84        .get_message(msgid)
85        .ok_or_else(|| anyhow!("Message `{}` was not found.", msgid))?;
86    let pattern = msg
87        .value()
88        .ok_or_else(|| anyhow!("Message `{}` has no value.", msgid))?;
89    Ok(bundle
90        .format_pattern(pattern, args.as_ref(), &mut errors)
91        .into())
92}
93
94pub fn get_message_global(msgid: &str, langid: &str, args: Option<FluentArgs>) -> String {
95    if let Some(bundle) = BUNDLES.get(langid) {
96        get_message(bundle, msgid, args).unwrap_or_else(|_| msgid.to_string())
97    } else {
98        msgid.to_string()
99    }
100}