pabitell_lib/
translations.rs1use 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}