Skip to content

Commit b1b937f

Browse files
authored
Merge pull request #345 from estefysc/feature/310-locale-settings
Feature/310 locale settings
2 parents cbb3675 + 8e2e8a3 commit b1b937f

20 files changed

+268
-5
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- Add locale field to admin_users table
2+
-- This allows each admin user to set their preferred language/locale
3+
DEFINE FIELD locale ON TABLE admin_users TYPE string DEFAULT "en";

proto/admin_user.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ message AdminUserModel {
1616
string created_by = 8;
1717
string updated_by = 9;
1818
repeated RoleModel roles = 10;
19+
string locale = 11;
1920
}
2021

2122

@@ -65,6 +66,7 @@ message StoreAdminUserRequest {
6566
bool is_super_admin = 5;
6667
bytes profile_image_content = 6;
6768
string profile_image_file_name = 7;
69+
string locale = 8;
6870
}
6971

7072
message StoreAdminUserResponse {
@@ -91,6 +93,7 @@ message UpdateAdminUserRequest {
9193
string profile_image_file_name = 4;
9294
repeated string role_ids = 5;
9395
bool is_super_admin = 6;
96+
string locale = 7;
9497
}
9598

9699
message UpdateAdminUserResponse {

src/api/admin_user_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ impl AdminUser for AdminUserApi {
9797
let request_data = request.into_inner();
9898
request_data.validate(&self.state).await?;
9999

100+
100101
match self
101102
.state
102103
.admin_user_service

src/api/misc_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ impl Misc for MiscApi {
386386
is_super_admin: false,
387387
profile_image_content: vec![],
388388
profile_image_file_name: String::new(),
389+
locale: String::from("en"),
389390
};
390391

391392
let created_admin_user = self

src/api/proto/admin_user.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub struct AdminUserModel {
2121
pub updated_by: ::prost::alloc::string::String,
2222
#[prost(message, repeated, tag = "10")]
2323
pub roles: ::prost::alloc::vec::Vec<RoleModel>,
24+
#[prost(string, tag = "11")]
25+
pub locale: ::prost::alloc::string::String,
2426
}
2527
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
2628
pub struct RoleModel {
@@ -96,6 +98,8 @@ pub struct StoreAdminUserRequest {
9698
pub profile_image_content: ::prost::alloc::vec::Vec<u8>,
9799
#[prost(string, tag = "7")]
98100
pub profile_image_file_name: ::prost::alloc::string::String,
101+
#[prost(string, tag = "8")]
102+
pub locale: ::prost::alloc::string::String,
99103
}
100104
#[derive(Clone, PartialEq, ::prost::Message)]
101105
pub struct StoreAdminUserResponse {
@@ -130,6 +134,8 @@ pub struct UpdateAdminUserRequest {
130134
pub role_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
131135
#[prost(bool, tag = "6")]
132136
pub is_super_admin: bool,
137+
#[prost(string, tag = "7")]
138+
pub locale: ::prost::alloc::string::String,
133139
}
134140
#[derive(Clone, PartialEq, ::prost::Message)]
135141
pub struct UpdateAdminUserResponse {

src/models/admin_user_model.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub struct AdminUserModel {
4646

4747
/// The roles assigned to the admin user.
4848
pub roles: Vec<RoleModel>,
49+
50+
/// The preferred locale/language for the admin user.
51+
pub locale: String,
4952
}
5053

5154
// region: impl try_from AdminUserModel
@@ -99,6 +102,7 @@ impl TryFrom<AdminUserModel> for GrpcAdminUserModel {
99102
created_by: val.created_by,
100103
updated_by: val.updated_by,
101104
roles: grpc_roles,
105+
locale: val.locale,
102106
};
103107

104108
Ok(model)
@@ -148,6 +152,7 @@ impl TryFrom<Object> for AdminUserModel {
148152
let updated_at = val.get("updated_at").get_datetime()?;
149153
let created_by = val.get("created_by").get_string()?;
150154
let updated_by = val.get("updated_by").get_string()?;
155+
let locale = val.get("locale").get_string().unwrap_or_else(|_| String::from("en"));
151156

152157
Ok(Self {
153158
id,
@@ -161,6 +166,7 @@ impl TryFrom<Object> for AdminUserModel {
161166
created_by,
162167
updated_by,
163168
roles,
169+
locale,
164170
})
165171
}
166172
}
@@ -188,6 +194,9 @@ pub struct CreatableAdminUserModel {
188194

189195
/// The username of the user who is creating this admin user.
190196
pub logged_in_username: String,
197+
198+
/// The preferred locale/language for the admin user.
199+
pub locale: String,
191200
// pub role_ids: Vec<String>,
192201
}
193202

@@ -202,7 +211,7 @@ pub struct UpdatableAdminUserModel {
202211

203212
/// The email address of the admin user.
204213
pub profile_image: String,
205-
214+
206215
/// is the admin user has super admin privileges.
207216
pub is_super_admin: bool,
208217

@@ -211,6 +220,9 @@ pub struct UpdatableAdminUserModel {
211220

212221
/// The roles assigned to the admin user.
213222
pub role_ids: Vec<String>,
223+
224+
/// The preferred locale/language for the admin user.
225+
pub locale: String,
214226
}
215227

216228
// /// Represents a paginated response for admin users.

src/repositories/admin_user_repository.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ impl AdminUserRepository {
122122
"is_super_admin".into(),
123123
creatable_admin_user_model.is_super_admin.into(),
124124
),
125+
(
126+
"locale".into(),
127+
creatable_admin_user_model.locale.into(),
128+
),
125129
(
126130
"created_by".into(),
127131
creatable_admin_user_model.logged_in_username.clone().into(),
@@ -160,6 +164,7 @@ impl AdminUserRepository {
160164
full_name: $full_name,
161165
profile_image: $profile_image,
162166
is_super_admin: $is_super_admin,
167+
locale: $locale,
163168
updated_by: $logged_in_user_name,
164169
updated_at: time::now()
165170
};";
@@ -178,6 +183,7 @@ impl AdminUserRepository {
178183
"is_super_admin".into(),
179184
updatable_admin_user.is_super_admin.into(),
180185
),
186+
("locale".into(), updatable_admin_user.locale.into()),
181187
("id".into(), updatable_admin_user.id.into()),
182188
("table".into(), ADMIN_USER_TABLE.into()),
183189
]);

src/requests/admin_user_request/store_admin_user_request.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::api::proto::admin_user::StoreAdminUserRequest;
22
use crate::avored_state::AvoRedState;
33
use crate::models::validation_error::{ErrorMessage, ErrorResponse, Validate};
44
use rust_i18n::t;
5+
use std::path::Path;
56

67
impl StoreAdminUserRequest {
78
/// validate
@@ -63,6 +64,20 @@ impl StoreAdminUserRequest {
6364
errors.push(error_message);
6465
}
6566

67+
// Validate locale if provided (strict validation)
68+
if !self.locale.is_empty() {
69+
let locale_path = Path::new("resources/locales").join(format!("{}.json", self.locale));
70+
if !locale_path.exists() {
71+
let error_message = ErrorMessage {
72+
key: String::from("locale"),
73+
message: t!("validation_invalid", attribute = t!("locale")).to_string(),
74+
};
75+
76+
valid = false;
77+
errors.push(error_message);
78+
}
79+
}
80+
6681
if !valid {
6782
let error_response = ErrorResponse {
6883
status: valid,

src/requests/admin_user_request/update_admin_user_request.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::api::proto::admin_user::UpdateAdminUserRequest;
22
use crate::models::validation_error::{ErrorMessage, ErrorResponse, Validate};
33
use rust_i18n::t;
4+
use std::path::Path;
45

56
impl UpdateAdminUserRequest {
67
/// validate
@@ -18,6 +19,20 @@ impl UpdateAdminUserRequest {
1819
errors.push(error_message);
1920
}
2021

22+
// Validate locale if provided (strict validation)
23+
if !self.locale.is_empty() {
24+
let locale_path = Path::new("resources/locales").join(format!("{}.json", self.locale));
25+
if !locale_path.exists() {
26+
let error_message = ErrorMessage {
27+
key: String::from("locale"),
28+
message: t!("validation_invalid", attribute = t!("locale")).to_string(),
29+
};
30+
31+
valid = false;
32+
errors.push(error_message);
33+
}
34+
}
35+
2136
if !valid {
2237
let error_response = ErrorResponse {
2338
status: valid,

src/services/admin_user_service.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,22 @@ impl AdminUserService {
9494
let password_hash =
9595
self.get_password_hash_from_raw_password(&req.password, password_salt)?;
9696

97+
// Use provided locale or default to "en" if empty
98+
// Request layer already validated that locale exists
99+
let locale = if req.locale.is_empty() {
100+
String::from("en")
101+
} else {
102+
req.locale.clone()
103+
};
104+
97105
let mut created_admin_user_model = CreatableAdminUserModel {
98106
full_name: req.full_name,
99107
email: req.email,
100108
password: password_hash,
101109
profile_image: String::new(),
102110
is_super_admin: req.is_super_admin,
103111
logged_in_username,
112+
locale,
104113
};
105114

106115
if !req.profile_image_file_name.is_empty() {
@@ -153,13 +162,23 @@ impl AdminUserService {
153162
logged_in_username: String,
154163
(datastore, database_session): &DB,
155164
) -> Result<UpdateAdminUserResponse> {
165+
166+
// Use provided locale or default to "en" if empty
167+
// Request layer already validated that locale exists
168+
let locale = if req.locale.is_empty() {
169+
String::from("en")
170+
} else {
171+
req.locale.clone()
172+
};
173+
156174
let mut updatable_admin_user_model = UpdatableAdminUserModel {
157175
id: req.admin_user_id,
158176
full_name: req.full_name,
159177
profile_image: String::new(),
160178
is_super_admin: req.is_super_admin,
161179
logged_in_username: logged_in_username.clone(),
162180
role_ids: req.role_ids,
181+
locale,
163182
};
164183

165184
if !req.profile_image_file_name.is_empty() {

0 commit comments

Comments
 (0)