Skip to content

Commit 649a65d

Browse files
committed
feat(i18n): ✨ added fn on translations manager to get string translations
Avoids unnecessary deserialize-then-serialize.
1 parent fc8eeaf commit 649a65d

File tree

3 files changed

+38
-25
lines changed

3 files changed

+38
-25
lines changed

packages/perseus-actix-web/src/translations.rs

+7-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::Options;
22
use actix_web::{web, HttpRequest, HttpResponse};
33
use perseus::TranslationsManager;
44

5-
/// The handler for calls to `.perseus/page/*`. This will manage returning errors and the like.
5+
/// The handler for calls to `.perseus/translations/{locale}`. This will manage returning errors and the like. THe JSON body returned
6+
/// from this does NOT include the `locale` key, just a `HashMap<String, String>` of the translations themselves.
67
pub async fn translations<T: TranslationsManager>(
78
req: HttpRequest,
89
opts: web::Data<Options>,
@@ -11,23 +12,14 @@ pub async fn translations<T: TranslationsManager>(
1112
let locale = req.match_info().query("locale");
1213
// Check if the locale is supported
1314
if opts.locales.is_supported(locale) {
14-
// Create a translator for that locale (and hope the implementation is caching for sanity)
15-
// We know that it''s supported, which means a failure is a 500
16-
let translator = translations_manager
17-
.get_translator_for_locale(locale.to_string())
18-
.await;
19-
let translator = match translator {
20-
Ok(translator) => translator,
21-
Err(err) => return HttpResponse::InternalServerError().body(err.to_string()),
22-
};
23-
// Serialize that into a JSON response
24-
let json = serde_json::to_string(&translator);
25-
let json = match json {
26-
Ok(json) => json,
15+
// We know that the locale is supported, so any failure to get translations is a 500
16+
let translations = translations_manager.get_translations_str_for_locale(locale.to_string()).await;
17+
let translations = match translations {
18+
Ok(translations) => translations,
2719
Err(err) => return HttpResponse::InternalServerError().body(err.to_string()),
2820
};
2921

30-
HttpResponse::Ok().body(json)
22+
HttpResponse::Ok().body(translations)
3123
} else {
3224
HttpResponse::NotFound().body("locale not supported".to_string())
3325
}

packages/perseus/src/client_translations_manager.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::shell::fetch;
33
use crate::Locales;
44
use crate::Translator;
55
use std::rc::Rc;
6+
use std::collections::HashMap;
67

78
/// Manages translations in the app shell. This handles fetching translations from the server as well as caching for performance.
89
/// This is distinct from `TranslationsManager` in that it operates on the client-side rather than on the server. This optimizes for
@@ -39,10 +40,11 @@ impl ClientTranslationsManager {
3940
let translator = match translator_str {
4041
Ok(translator_str) => match translator_str {
4142
Some(translator_str) => {
42-
// All good, deserialize the translator
43-
let translator = serde_json::from_str::<Translator>(&translator_str);
44-
match translator {
45-
Ok(translator) => translator,
43+
// All good, deserialize the translations
44+
let translations = serde_json::from_str::<HashMap<String, String>>(&translator_str);
45+
match translations {
46+
// And then turn them into a translator
47+
Ok(translations) => Translator::new(locale.to_string(), translations),
4648
Err(err) => {
4749
bail!(ErrorKind::AssetSerFailed(asset_url, err.to_string()))
4850
}

packages/perseus/src/translations_manager.rs

+25-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::Translator;
66
use error_chain::{bail, error_chain};
77
use std::collections::HashMap;
88
use std::fs;
9+
use std::sync::Arc;
910

1011
// This has no foreign links because everything to do with config management should be isolated and generic
1112
error_chain! {
@@ -29,12 +30,15 @@ error_chain! {
2930
}
3031
}
3132

32-
/// A trait for systems that manage where to put configuration files. At simplest, we'll just write them to static files, but they're
33-
/// more likely to be stored on a CMS.
33+
/// A trait for systems that manage where to put translations. At simplest, we'll just write them to static files, but they might also
34+
/// be stored in a CMS.
3435
#[async_trait::async_trait]
3536
pub trait TranslationsManager: Clone {
36-
/// Gets translations for the given locale.
37+
/// Gets a translator for the given locale.
3738
async fn get_translator_for_locale(&self, locale: String) -> Result<Translator>;
39+
/// Gets the translations in stirng format for the given locale (avoids deserialize-then-serialize). This should NOT include the
40+
/// translator's `locale` property, it should simply be a `HashMap<String, String>` in JSON format.
41+
async fn get_translations_str_for_locale(&self, locale: String) -> Result<String>;
3842
}
3943

4044
/// The default translations manager. This will store static files in the specified location on disk. This should be suitable for
@@ -43,16 +47,26 @@ pub trait TranslationsManager: Clone {
4347
#[derive(Clone)]
4448
pub struct FsTranslationsManager {
4549
root_path: String,
50+
/// A map of locales to cached translators. This decreases the number of file reads significantly for the locales specified. This
51+
/// does NOT cache dynamically, and will only cache the requested locales.
52+
cached_translators: HashMap<String, Arc<Translator>>,
53+
/// The locales being cached for easier access.
54+
cached_locales: Vec<String>
4655
}
56+
// TODO implement caching
4757
impl FsTranslationsManager {
4858
/// Creates a new filesystem translations manager. You should provide a path like `/translations` here.
4959
pub fn new(root_path: String) -> Self {
50-
Self { root_path }
60+
Self {
61+
root_path,
62+
cached_translators: HashMap::new(),
63+
cached_locales: Vec::new()
64+
}
5165
}
5266
}
5367
#[async_trait::async_trait]
5468
impl TranslationsManager for FsTranslationsManager {
55-
async fn get_translator_for_locale(&self, locale: String) -> Result<Translator> {
69+
async fn get_translations_str_for_locale(&self, locale: String) -> Result<String> {
5670
// The file must be named as the locale it describes
5771
let asset_path = format!("{}/{}.json", self.root_path, locale);
5872
let translations_str = match fs::metadata(&asset_path) {
@@ -63,9 +77,14 @@ impl TranslationsManager for FsTranslationsManager {
6377
}
6478
Err(err) => bail!(ErrorKind::ReadFailed(locale.to_string(), err.to_string())),
6579
};
80+
81+
Ok(translations_str)
82+
}
83+
async fn get_translator_for_locale(&self, locale: String) -> Result<Translator> {
84+
let translations_str = self.get_translations_str_for_locale(locale.clone()).await?;
6685
// We expect the translations defined there, but not the locale itself
6786
let translations = serde_json::from_str::<HashMap<String, String>>(&translations_str)
68-
.map_err(|err| ErrorKind::SerializationFailed(locale.to_string(), err.to_string()))?;
87+
.map_err(|err| ErrorKind::SerializationFailed(locale.clone(), err.to_string()))?;
6988
let translator = Translator::new(locale, translations);
7089

7190
Ok(translator)

0 commit comments

Comments
 (0)