Skip to content

Commit 24f4362

Browse files
committed
refactor(i18n): 🚚 refactored to prepare for future multi-translator support
1 parent 89fa00e commit 24f4362

File tree

8 files changed

+78
-70
lines changed

8 files changed

+78
-70
lines changed

‎packages/perseus/src/errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ error_chain! {
7676
links {
7777
ConfigManager(crate::config_manager::Error, crate::config_manager::ErrorKind);
7878
TranslationsManager(crate::translations_manager::Error, crate::translations_manager::ErrorKind);
79-
Translator(crate::translator::Error, crate::translator::ErrorKind);
79+
Translator(crate::translator::errors::Error, crate::translator::errors::ErrorKind);
8080
}
8181
// We work with many external libraries, all of which have their own errors
8282
foreign_links {

‎packages/perseus/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mod client_translations_manager;
3535
pub mod config_manager;
3636
mod decode_time_str;
3737
pub mod errors;
38+
mod locales;
3839
mod macros;
3940
/// Utilities for serving your app. These are platform-agnostic, and you probably want an integration like [perseus-actix-web](https://crates.io/crates/perseus-actix-web).
4041
pub mod serve;
@@ -58,8 +59,9 @@ pub use crate::build::{build_app, build_template, build_templates_for_locale};
5859
pub use crate::client_translations_manager::ClientTranslationsManager;
5960
pub use crate::config_manager::{ConfigManager, FsConfigManager};
6061
pub use crate::errors::{err_to_status_code, ErrorCause};
62+
pub use crate::locales::Locales;
6163
pub use crate::serve::{get_page, get_render_cfg};
6264
pub use crate::shell::{app_shell, ErrorPages};
6365
pub use crate::template::{States, StringResult, StringResultWithCause, Template, TemplateMap};
6466
pub use crate::translations_manager::{FsTranslationsManager, TranslationsManager};
65-
pub use crate::translator::{Locales, Translator};
67+
pub use crate::translator::{Translator, TRANSLATOR_FILE_EXT};

‎packages/perseus/src/locales.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// Defines app information about i18n, specifically about which locales are supported.
2+
#[derive(Clone)]
3+
pub struct Locales {
4+
/// The default locale, which will be used as a fallback if the user's locale can't be extracted. This will be built for at build-time.
5+
pub default: String,
6+
/// All other supported locales, which will all be built at build time.
7+
pub other: Vec<String>,
8+
}
9+
impl Locales {
10+
/// Gets all the supported locales by combining the default, and other.
11+
pub fn get_all(&self) -> Vec<&String> {
12+
let mut vec: Vec<&String> = vec![&self.default];
13+
vec.extend(&self.other);
14+
15+
vec
16+
}
17+
/// Checks if the given locale is supported.
18+
pub fn is_supported(&self, locale: &str) -> bool {
19+
let locales = self.get_all();
20+
locales.iter().any(|l| *l == locale)
21+
}
22+
}

‎packages/perseus/src/macros.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ macro_rules! define_get_translations_manager {
3131
$crate::FsTranslationsManager::new(
3232
"../translations".to_string(),
3333
all_locales,
34-
"ftl".to_string(),
34+
$crate::TRANSLATOR_FILE_EXT.to_string(),
3535
)
3636
.await
3737
}

‎packages/perseus/src/translations_manager.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async fn get_translations_str_and_cache(
6060

6161
/// The default translations manager. This will store static files in the specified location on disk. This should be suitable for
6262
/// nearly all development and serverful use-cases. Serverless is another matter though (more development needs to be done). This
63-
/// mandates that translations be stored as files named as the locale they describe (e.g. 'en-US.ftl').
63+
/// mandates that translations be stored as files named as the locale they describe (e.g. 'en-US.ftl', 'en-US.json', etc.).
6464
#[derive(Clone)]
6565
pub struct FsTranslationsManager {
6666
root_path: String,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
pub use error_chain::bail;
2+
use error_chain::error_chain;
3+
4+
// This has its own error management because the user might be implementing translators themselves
5+
error_chain! {
6+
errors {
7+
/// For when a translation ID doesn't exist.
8+
TranslationIdNotFound(id: String, locale: String) {
9+
description("translation id not found for current locale")
10+
display("translation id '{}' not found for locale '{}'", id, locale)
11+
}
12+
/// For when the given string of translations couldn't be correctly parsed
13+
TranslationsStrSerFailed(locale: String, err: String) {
14+
description("given translations string couldn't be parsed")
15+
display("given translations string for locale '{}' couldn't be parsed: '{}'", locale, err)
16+
}
17+
/// For when the given locale was invalid. This takes an error because different i18n systems may have different requirements.
18+
InvalidLocale(locale: String, err: String) {
19+
description("given locale was invalid")
20+
display("given locale '{}' was invalid: '{}'", locale, err)
21+
}
22+
/// For when the translation of a message failed for some reason generally.
23+
TranslationFailed(id: String, locale: String, err: String) {
24+
description("message translation failed")
25+
display("translation of message with id '{}' into locale '{}' failed: '{}'", id, locale, err)
26+
}
27+
/// For when the we couldn't arrive at a translation for some reason. This might be caused by an invalid variant for a compound
28+
/// message.
29+
NoTranslationDerived(id: String, locale: String) {
30+
description("no translation derived for message")
31+
display("no translation could be derived for message with id '{}' in locale '{}'", id, locale)
32+
}
33+
}
34+
}

‎packages/perseus/src/translator.rs renamed to ‎packages/perseus/src/translator/fluent.rs

+5-66
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,24 @@
1-
use error_chain::{bail, error_chain};
1+
use crate::translator::errors::*;
22
use fluent_bundle::{FluentArgs, FluentBundle, FluentResource};
33
use std::rc::Rc;
44
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};
55

6-
/// Defines app information about i18n, specifically about which locales are supported.
7-
#[derive(Clone)]
8-
pub struct Locales {
9-
/// The default locale, which will be used as a fallback if the user's locale can't be extracted. This will be built for at build-time.
10-
pub default: String,
11-
/// All other supported locales, which will all be built at build time.
12-
pub other: Vec<String>,
13-
}
14-
impl Locales {
15-
/// Gets all the supported locales by combining the default, and other.
16-
pub fn get_all(&self) -> Vec<&String> {
17-
let mut vec: Vec<&String> = vec![&self.default];
18-
vec.extend(&self.other);
19-
20-
vec
21-
}
22-
/// Checks if the given locale is supported.
23-
pub fn is_supported(&self, locale: &str) -> bool {
24-
let locales = self.get_all();
25-
locales.iter().any(|l| *l == locale)
26-
}
27-
}
28-
29-
// This has its own error management because the user might be implementing translators themselves
30-
error_chain! {
31-
errors {
32-
/// For when a translation ID doesn't exist.
33-
TranslationIdNotFound(id: String, locale: String) {
34-
description("translation id not found for current locale")
35-
display("translation id '{}' not found for locale '{}'", id, locale)
36-
}
37-
/// For when the given string of translations couldn't be correctly parsed
38-
TranslationsStrSerFailed(locale: String, err: String) {
39-
description("given translations string couldn't be parsed")
40-
display("given translations string for locale '{}' couldn't be parsed: '{}'", locale, err)
41-
}
42-
/// For when the given locale was invalid. This takes an error because different i18n systems may have different requirements.
43-
InvalidLocale(locale: String, err: String) {
44-
description("given locale was invalid")
45-
display("given locale '{}' was invalid: '{}'", locale, err)
46-
}
47-
/// For when the translation of a message failed for some reason generally.
48-
TranslationFailed(id: String, locale: String, err: String) {
49-
description("message translation failed")
50-
display("translation of message with id '{}' into locale '{}' failed: '{}'", id, locale, err)
51-
}
52-
/// For when the we couldn't arrive at a translation for some reason. This might be caused by an invalid variant for a compound
53-
/// message.
54-
NoTranslationDerived(id: String, locale: String) {
55-
description("no translation derived for message")
56-
display("no translation could be derived for message with id '{}' in locale '{}'", id, locale)
57-
}
58-
}
59-
}
6+
/// The file extension used by the Fluent translator, which expects FTL files.
7+
pub const FLUENT_TRANSLATOR_FILE_EXT: &str = "ftl";
608

619
/// Manages translations on the client-side for a single locale using Mozilla's [Fluent](https://projectfluent.org/) syntax. This
6210
/// should generally be placed into an `Rc<T>` and referred to by every template in an app. You do NOT want to be cloning potentially
6311
/// thousands of translations!
6412
///
6513
/// Fluent supports compound messages, with many variants, which can specified here using the form `[id].[variant]` in a translation ID,
6614
/// as a `.` is not valid in an ID anyway, and so can be used as a delimiter. More than one dot will result in an error.
67-
pub struct Translator {
15+
pub struct FluentTranslator {
6816
/// Stores the internal Fluent data for translating. This bundle directly owns its attached resources (translations).
6917
bundle: Rc<FluentBundle<FluentResource>>,
7018
/// The locale for which translations are being managed by this instance.
7119
locale: String,
7220
}
73-
impl Translator {
21+
impl FluentTranslator {
7422
/// Creates a new translator for a given locale, passing in translations in FTL syntax form.
7523
pub fn new(locale: String, ftl_string: String) -> Result<Self> {
7624
let resource = FluentResource::try_new(ftl_string)
@@ -197,12 +145,3 @@ impl Translator {
197145
Rc::clone(&self.bundle)
198146
}
199147
}
200-
201-
/// A super-shortcut for translating stuff. Your translator must be named `translator` for this to work.
202-
// FIXME
203-
#[macro_export]
204-
macro_rules! t {
205-
($id:literal, $translator:expr) => {
206-
$translator.translate($id)
207-
};
208-
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Errors for translators. These are separate so new translators can easily be created in a modular fashion.
2+
pub mod errors;
3+
mod fluent;
4+
5+
// We export each translator by name
6+
pub use fluent::{FluentTranslator, FLUENT_TRANSLATOR_FILE_EXT};
7+
8+
// And then we export defaults using feature gates
9+
// TODO feature-gate these lines
10+
pub use FluentTranslator as Translator;
11+
pub use FLUENT_TRANSLATOR_FILE_EXT as TRANSLATOR_FILE_EXT;

0 commit comments

Comments
 (0)